题目
思路
问题翻译一下:求前缀 max \max max 的变化次数。
首先看看我们怎么减小计算的次数:比如原本是单调递增,然后把中间某个值突然调大,然后又调小。显然调小的时候,涉及一个问题是,想要立刻把后面原有的序列接上去。怎么维护呢?
我们会发现,可能接上去的序列其实是:从此处将序列割裂后,后半部分的所有前缀 max \max max 的位置。然而所有可能的割裂点都求出来,这是不现实的。那么我们考虑一下,如果割裂点不是被更改的元素,又怎么样呢?
如果被更改的元素在右半部分,那么左半部分不需要做任何调整,我们可以试图对右半部分 递归,重新得到前缀 max \max max 的位置,然后求答案;如果被更改的元素在左半部分,那么右半部分得到的前缀 max \max max 的取值点不会变,我们可以试图对左半部分 递归计算,然后右半部分同样直接求出可以接上去的序列。
可是我们很悲催地发现,前缀 max \max max 的更改是非常多的……怎么办?有一个非常大胆的想法:当我们知道合并规则的时候,未必要显式合并,只需要 在提取信息时模拟合并过程。比如我们要求出右半部分可以接上去多少,相当于问右端点的前缀 max \max max 的一段后缀(大于左半部分最大值);而我们又知道,右半部分的前缀 max \max max 是它的两个部分拼接起来得到的;所以当这个前缀只涉及右侧时,往右半部分递归;否则完全包含右侧,直接加上右侧的点数,然后往左半部分递归。
形式化地:建线段树,每个点维护右儿子的前缀 max \max max 数量 c u c_u cu 。定义函数 f ( x ) f(x) f(x) 为前缀 max \max max 中超过 x x x 的值的数量,记 v l v_l vl 为左儿子的区间最大值,那么 v l ⩽ x v_l\leqslant x vl⩽x 时, f ( x ) = f r s o n ( x ) f(x)=f_{rson}(x) f(x)=frson(x) 。当 v l > x v_l>x vl>x 时, f ( x ) = f l s o n ( x ) + c u f(x)=f_{lson}(x)+c_u f(x)=flson(x)+cu 。所以计算 f ( x ) f(x) f(x) 的复杂度是 O ( log n ) \mathcal O(\log n) O(logn) 的。
而 c u c_u cu 也要用 f ( x ) f(x) f(x) 求解,共 O ( log n ) \mathcal O(\log n) O(logn) 个值,所以复杂度 O ( n log 2 n ) \mathcal O(n\log^2 n) O(nlog2n) 。
那么这道题算不算一定要先想到线段树,再想到解法的题呢?感觉也不是吧。只是 motivation \text{motivation} motivation 不太强烈啊……
代码
#include <cstdio> // XJX yyds!!!
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
void writeint(int x){
if(x > 9) writeint(x/10);
putchar(char((x%10)^48));
}
const int MAXN = 100005;
int n;
namespace SgTree{
int val[MAXN<<2]; double mx[MAXN<<2];
# define _ROOT_ int o=1,int l=1,int r=n
# define LSON o<<1,l,((l+r)>>1)
# define RSON o<<1|1,((l+r)>>1)+1,r
int _calc(const double &qv,_ROOT_){
if(l == r) return bool(mx[o] > qv);
if(mx[o<<1] <= qv) return _calc(qv,RSON);
return _calc(qv,LSON)+val[o];
}
void modify(int qid,double qv,_ROOT_){
if(l == r) return void(mx[o] = qv);
if((qid<<1) <= l+r) modify(qid,qv,LSON);
else modify(qid,qv,RSON);
mx[o] = max(mx[o<<1],mx[o<<1|1]);
val[o] = _calc(mx[o<<1],RSON);
}
inline int query(_ROOT_){
return _calc(0,LSON)+val[o];
}
}
int main(){
n = readint();
int m = readint();
for(int x,y; m; --m){
x = readint(), y = readint();
SgTree::modify(x,double(y)/x);
writeint(SgTree::query()), putchar('\n');
}
return 0;
}