第二分块学习笔记

问题引入:CF896E
首先将原序列分块。
先看修改:
对于边角块直接暴力改,之后重构,至于重构什么下文会提及。
但整块的并不容易用什么东西直接维护。
观察到修改操作的值域较小,且每次修改后区间最大值单调不增,
于是每一块修改时可以考虑将区间最大值作为势能搞点事:
定义势能函数 \(\Phi=块内最大值mx\)
对每次修改的 \(v\) 进行分类讨论:

  1. 若 \(v\geq mx\) 啥事都没有,直接过
  2. 若 \(v\leq 2mx\) 此时 \(mx\) 至少会变成 \(v\) ,\(\Delta \Phi\geq -(mx-v)\),
    于是可以允许在 \(O(mx-v)\) 的时间内将全部在 \(v+1\) 到 \(mx\) 间的元素变成它减 \(v\) 。
    这之后仍然难以知道 \(mx\) 最终变成什么,但只需让其变为 \(v\) 也能保证时间复杂度。
  3. 若 \(v>2mx\) 此时 \(mx\) 将会减去 \(v\) , \(\Delta \Phi=-v\),
    这时要减的数将会十分多,就可以反过来考虑,以 \(O(v)\) 的时间将 \(1\) 到 \(v\) 间的元素变成它加 \(v\) ,
    再在这个块中打上区间减 \(v\) 的标记即可,这样 \(mx\) 仍然不动,查的时候减掉减的标记就行。

这样对于每个块最终的摊还时间就是 \(O(V)\) 的,\(V\) 代表值域最大值,
所有块的时间总和 \(O(V\times n^{\frac{1}{2}})\) 是能忍受的。
这个时间还要乘上实现区间内对每个元素变成另一个所用数据结构的时间。
但在查询时真正需要的却是某一位置上的真正的值。
这时就可以将值相同的位置仍进一个并查集里面,
记录并查集的根(根是一个位置)所代表的真实的值以及其个数
当把某个值变成另一个时就相当于合并两个并查集,同时必须清空原先值的根以及个数。
由于并查集中记录的父亲仍是位置,而位置两两不同,就确保并查集的合并不会让其他位置获取值时受影响
这样对每个块中的每个值都记录根和个数的空间复杂度会去到 \(O(n^{\frac{3}{2}})\) 但仍开得下。
对散块的重构就要先在并查集上获取值,并清除得到的值的根及个数(并查集上间接跳的值在合并时已清除),
清除完之后再减去减的标记,这是因为并查集上维护的值是基于减的标记之前的,之后将减的标记清空。
在修改完值后重新求每个值的根及个数,还有区间最大值,同时处理出每个位置在并查集上的父亲。
事实上由于对散块修改的位置范围不同,上述重构可看做清空与重构两个步骤。
最终的时间复杂度是 \(O(n^{\frac{3}{2}}\alpha(n))\) 的。
代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,NN=400;
int n,m,x,y,v,nn,sn,ans,res,opt;
int rt[NN][N],cnt[NN][N];char ch;
int id[N],l[N],r[N],f[N],w[N],a[N],mns[N],mx[N];
inline void read(int &x){
	x=0;ch=getchar();while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
}
inline void write(int x){if(x>=10)write(x/10);putchar('0'+x%10);}
inline int getf(int x){return x==f[x]?x:f[x]=getf(f[f[x]]);}
inline void merge(int p,int x,int y){
	if(rt[p][y])f[rt[p][x]]=rt[p][y];
	else rt[p][y]=rt[p][x],w[rt[p][y]]=y;
	cnt[p][y]+=cnt[p][x];rt[p][x]=cnt[p][x]=0;
}
inline void pushdown(int p){
	register int i;
	for(i=l[p];i<=r[p];++i)a[i]=w[getf(i)],rt[p][a[i]]=cnt[p][a[i]]=0;
	for(i=l[p];i<=r[p];++i)a[i]-=mns[p],f[i]=0;mns[p]=0;
}
inline void reset(int p,int x=0,int y=0){
	register int i;mx[p]=0;
	for(i=l[p];i<=r[p];++i){
		mx[p]=max(mx[p],a[i]);
		if(rt[p][a[i]])f[i]=rt[p][a[i]];
		else w[i]=a[i],rt[p][a[i]]=f[i]=i;
		cnt[p][a[i]]++;
	}
}
inline void modify(int x,int y,int v){
	register int i;
	if(id[x]==id[y]){pushdown(id[x]);for(i=x;i<=y;++i)if(a[i]>v)a[i]-=v;reset(id[x],x,y);}
	else {
		pushdown(id[x]);for(i=x;id[i]==id[x];++i)if(a[i]>v)a[i]-=v;reset(id[x]);
		pushdown(id[y]);for(i=y;id[i]==id[y];--i)if(a[i]>v)a[i]-=v;reset(id[y]);
		int j;
		for(i=id[x]+1;i^id[y];++i)if(v<mx[i]-mns[i]){
			if(mx[i]-mns[i]<=2*v){
				for(j=mns[i]+v+1;j<=mx[i];++j)if(rt[i][j])merge(i,j,j-v);
				mx[i]=mns[i]+v;
			}
			else {for(j=mns[i]+1;j<=mns[i]+v;++j)if(rt[i][j])merge(i,j,j+v);mns[i]+=v;}
		}
	}
}
inline void inquiry(int x,int y,int v){
	register int i;res=0;
	if(id[x]==id[y]){for(i=x;i<=y;++i)if(w[getf(i)]-mns[id[x]]==v)++res;}
	else {
		for(i=x;id[i]==id[x];++i)if(w[getf(i)]-mns[id[x]]==v)++res;
		for(i=y;id[i]==id[y];--i)if(w[getf(i)]-mns[id[y]]==v)++res;
		for(i=id[x]+1;i^id[y];++i)if(v+mns[i]<N)res+=cnt[i][v+mns[i]];
	}
}
main(){
	read(n),read(m);nn=250;sn=(n-1)/nn+1;register int i;
	for(i=1;i<=n;++i)read(a[i]),id[i]=(i-1)/nn+1;
	for(i=1;i<=n;++i)r[id[i]]=i,l[id[n-i+1]]=n-i+1;
	for(i=1;i<=sn;++i)reset(i);
	while(m--){
		read(opt),read(x),read(y),read(v);
		if(opt==1)modify(x,y,v);
		else inquiry(x,y,v),write(res),putchar('\n');
	}
}

但第二分块不仅于此。
CF 那题只是lxl出给CF的弱化版,还给许多暴力的过了。
这对于lxl显然丝毫不可容忍,于是有了这题:P4117五彩斑斓的世界
数据范围大了一些,但更重要的是空间限制变成了 \(\text{64.00MB}\)
但之前的方法仍然可行。
由于询问为 \(个数\) ,具有可加性,这就可以对每个块分开统计。
将询问离线下来,并对每个块分别处理,这样就可以共用一个之前对块内每个值开的根和个数的数组,
空间会减小至 \(O(n)\)。
于是此题可以愉快地在一波卡常后过了:
代码(其实也没差多少):

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,m,nn,sn,mns,mx,res;
int l[N],r[N],id[N];
int a[N],f[N],cnt[N],rt[N],w[N];char ch;
struct inq{int opt,x,y,v;}q[N];int ans[N];
inline void read(int &x){
	x=0;ch=getchar();while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')x=x*10+ch-48,ch=getchar();
}
inline void write(int x){if(x>=10)write(x/10);putchar('0'+x%10);}
inline int getf(int x){return x==f[x]?x:f[x]=getf(f[f[x]]);}
inline void merge(int x,int y){
	if(rt[y])f[rt[x]]=rt[y];
	else rt[y]=rt[x],w[rt[y]]=y;
	cnt[y]+=cnt[x],rt[x]=cnt[x]=0;
}
inline void pushdown(int p){
	register int i;
	for(i=l[p];i<=r[p];++i)a[i]=w[getf(i)],rt[a[i]]=cnt[a[i]]=0;
	for(i=l[p];i<=r[p];++i)a[i]-=mns,f[i]=0;mns=0;
}
inline void reset(int p){
	register int i;mx=0;
	for(i=l[p];i<=r[p];++i){
		mx=max(mx,a[i]);
		if(rt[a[i]])f[i]=rt[a[i]];
		else w[i]=a[i],rt[a[i]]=f[i]=i;
		++cnt[a[i]];
	}
}
inline void modify(int x,int y,int v,int p){
	register int i;
	if(l[p]<=x&&y<=r[p]){pushdown(p);for(i=x;i<=y;++i)if(a[i]>v)a[i]-=v;reset(p);}
	else if(l[p]<=x&&x<=r[p]){pushdown(p);for(i=x;i<=r[p];++i)if(a[i]>v)a[i]-=v;reset(p);}
	else if(l[p]<=y&&y<=r[p]){pushdown(p);for(i=y;i>=l[p];--i)if(a[i]>v)a[i]-=v;reset(p);}
	else if(v<mx-mns){
		if(mx-mns<=2*v){for(i=mns+v+1;i<=mx;++i)if(rt[i])merge(i,i-v);mx=mns+v;}
		else {for(i=mns+1;i<=mns+v;++i)if(rt[i])merge(i,i+v);mns+=v;}
	}
}
inline void inquiry(int x,int y,int v,int p){
	res=0;register int i;
	if(l[p]<=x&&y<=r[p]){for(i=x;i<=y;++i)if(w[getf(i)]-mns==v)++res;}
	else if(l[p]<=x&&x<=r[p]){for(i=x;i<=r[p];++i)if(w[getf(i)]-mns==v)++res;}
	else if(l[p]<=y&&y<=r[p]){for(i=y;i>=l[p];--i)if(w[getf(i)]-mns==v)++res;}
	else if(v+mns<=1e5+1)res+=cnt[v+mns];
}
main(){
	read(n),read(m);nn=800;sn=(n-1)/nn+1;register int i,j;
	for(i=1;i<=n;++i)read(a[i]),id[i]=(i-1)/nn+1;
	for(i=1;i<=n;++i)r[id[i]]=i,l[id[n-i+1]]=n-i+1;
	for(i=1;i<=m;++i)read(q[i].opt),read(q[i].x),read(q[i].y),read(q[i].v);
	for(i=1;i<=sn;++i){
		reset(i);
		for(j=1;j<=m;++j){
			if(q[j].x>r[i]||q[j].y<l[i])continue;
			if(q[j].opt==1)modify(q[j].x,q[j].y,q[j].v,i);
			else inquiry(q[j].x,q[j].y,q[j].v,i),ans[j]+=res;
		}
		pushdown(i);
	}
	for(i=1;i<=m;++i)if(q[i].opt==2)write(ans[i]),putchar('\n');
}
上一篇:leetcode 回文子串 中等


下一篇:痞子衡嵌入式:如果你正在量产i.MX RT产品,不妨试试这款神器RT-Flash