1. 程式人生 > 其它 >第二分塊學習筆記

第二分塊學習筆記

問題引入: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');
}