不一样的差分+树状数组 Luogu P6912
这么臭的题有必要存在吗?
题意转化
题目所说的“先辈”序列,其实就是一个不下降序列。一个不下降序列,就是“先辈”。这个不再证明。
所以要求的就是:维护一个支持区间加的序列,可以判断区间是否为不下降序列。
实现方式
-
我们考虑简单的写法:用线段树维护。显然区间加可以用延迟标记来维护,这里不再赘述。
一个区间会被线段树分成 \(O(\log n)\) 个节点。如果这些节点中存在非“先辈”,那么整个区间一定非“先辈”。如果都是先辈,就判断每个节点的右端点是否小于等于下一个节点的左端点。显然线段树也能做到。
但是上述做法常数大,又难写,这里提供一种令人耳目一新的做法。
-
可以想到一个序列不降,它的差分序列一定非负。因此我们维护差分序列,是不是简单了很多?显然,区间加变成了单点加。区间查询,变成查询 \((l,r]\) 是否非负。(差分数组,所以要开区间。)
如何查询整个区间非负呢。容易想到的是查找整个区间的最小值,判断是否大于等于 \(0\)。可以用线段树 \(O(\log n)\) 维护,也可以用树状数组 \(O(\log^2 n)\) 维护。
但是巧妙的地方来了。我们可以用维护一个 \(01\) 序列 \(tr\)。具体地说,如果差分数组该位置是非负,记为 \(1\);否则记为 \(0\)。那么只需判断 \(\sum \limits_{i=l+1}^r{{tr}_i}\) 是否等于 \(r-l\) 即可。那么我们可以用一个树状数组维护前缀和得到区间和,常数小而代码短。
代码实现
#include<cstring>
#include<algorithm>
#include<iostream>
#define R myio::read_int()
//在后面的代码中,R 就代表读入。快读函数此处省略。
#define int long long
using namespace std;
const int N=1e6+6;
int n,k,op,l,r,x,c[N],tr[N];
int Sgn(int x){return x>=0;}
void Upd(int x,int d){for(;x<=n;x+=(x&(-x))) tr[x]+=d;}
int GET(int x){int s=0;for(;x;x-=(x&(-x))) s+=tr[x];return s;}
//树状数组维护前缀和
int GetSum(int L,int Ri){return GET(Ri)-GET(L);}
signed main(){
n=R,k=R;
for(int i=1,a,bf=0;i<=n;i++) {
c[i]=(a=R)-bf,bf=a;//c 数组储存差分的实际值
Upd(i,c[i]>=0?1:0);//初始化树状数组不要忘了
}while(k--){
op=R,l=R,r=min(R,n);
if(op==1){
x=R;
Upd(l,Sgn(c[l]+x)-Sgn(c[l]));
Upd(r+1,Sgn(c[r+1]-x)-Sgn(c[r+1]));
//判断前后是否出现正负变化
c[l]+=x,c[r+1]-=x;
}else {
if(GetSum(l,r)==r-l) puts("Yes");
//神之一手,原理请看上文
else puts("No");
}
}
return 0;
}
实测,这个代码跑得飞快,在众多代码中名列前茅。
思维方式
判断区间是否全满足某一条件时,问题都可以转化成,条件真假的区间和是否等于区间长度。