分块

分块

大家好,我非常喜欢暴力数据结构

就是把数组分成 \(\lceil \sqrt n \rceil\) 块,每一块的长度最大是 \(\lfloor \sqrt n \rfloor\)

左端点是 \(L_i\) ,右端点是 \(R_i\)\(i\) 所在的块是 \(id_i\)

对于一个区间查询 \([l,r]\),分成三个部分

首先 \(p=id_l,q=id_r\)

  • \(p=q\) 直接暴力
  • \(p\ne q\)
    1. \([l,R_p]\) 暴力处理
    2. \([L_q,r]\) 暴力处理
    3. \((p,q)\) 块,用懒标记或二分

时间复杂度 \(\sqrt n\)

使用范围: \(n<10^6\) ,或者代替一些高级的大常数数据结构

代替线段树

例:POJ - 3468:区间加,区间求和

边角暴力,整块打上懒标记即可

#include<cstdio>
#include<cmath>
using namespace std;
const int N=100005;
typedef long long LL;
int n,T,L[N],R[N],pos[N],tt;
LL x[N],tg[N],sm[N];
char opt[5];
inline void Change(int l,int r,LL vl) {
	register int p=pos[l],q=pos[r];
	if(p==q) {
		for(int i=l;i<=r;i++)x[i]+=vl;
		sm[p]+=vl*(r-l+1);
		return;
	}
	for(int i=p+1;i<=q-1;i++)tg[i]+=vl;
	for(int i=l;i<=R[p];i++)x[i]+=vl;
	sm[p]+=vl*(R[p]-l+1);
	for(int i=L[q];i<=r;i++)x[i]+=vl;
	sm[q]+=vl*(r-L[q]+1);
}
inline LL Ask(int l,int r) {
	register int p=pos[l],q=pos[r];
	register LL res=0;
	if(p==q) {
		for(int i=l;i<=r;i++)res+=x[i];
		res+=tg[p]*(r-l+1);
		return res;
	}
	for(int i=p+1;i<=q-1;i++)
		res+=sm[i]+tg[i]*(R[i]-L[i]+1);
	for(int i=l;i<=R[p];i++)res+=x[i];
	res+=tg[p]*(R[p]-l+1);
	for(int i=L[q];i<=r;i++)res+=x[i];
	res+=tg[q]*(r-L[q]+1);
	return res;
}
int main() {
	scanf("%d%d",&n,&T);
	for(int i=1;i<=n;i++)scanf("%lld",&x[i]);
	tt=sqrt(1.0*n);
	for(int i=1;i<=tt;i++) {
		L[i]=(i-1)*tt+1;
		R[i]=i*tt;
	}
	if(R[tt]<n)++tt,L[tt]=R[tt-1]+1,R[tt]=n;
	for(int i=1;i<=tt;i++)
		for(int j=L[i];j<=R[i];j++)
			pos[j]=i,sm[i]+=x[j];
	for(int l,r;T--;) {
		LL v;
		scanf("%s%d%d",opt,&l,&r);
		if(opt[0]=='C')scanf("%lld",&v),Change(l,r,v);
		else printf("%lld\n",Ask(l,r));
	}
}

代替主席树

例:Dynamic Rankings:带修主席树,查找区间第 \(k\)

块内排序,每次修改也排

二分答案,\(check\)\(mid\) 大的数的个数是否比 \(k\)

至于查询:边角暴力,块内二分

#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int n,T,sq,x[N],y[N],id[N],L[N],R[N];
char op[5];
inline void bui(int p) {
	for(int i=L[p];i<=R[p];i++)
		y[i]=x[i];
	sort(y+L[p],y+R[p]+1);
}
inline void mdy(int p,int v) {
	x[p]=v,bui(id[p]);
}
inline int get(int p,int v) {
	register int LL=L[p],RR=R[p];
	register int mid,ans=-1;
	while(LL<=RR) {
		mid=LL+RR>>1;
		if(y[mid]<=v)
			ans=mid,LL=mid+1;
		else RR=mid-1;
	}
	return ~ans?ans-L[p]+1:0;
}
inline int Ask(int l,int r,int k) {
	register int LL=0,RR=1e9,mid,ans=1;
 	register int p=id[l],q=id[r],cnt;
	while(LL<=RR) {
		mid=LL+RR>>1,cnt=0;
		if(p==q) {
			for(int i=l;i<=r;i++)
				if(x[i]<=mid)++cnt;
		} else {
			for(int i=l;i<=R[p];i++)
				if(x[i]<=mid)++cnt;
			for(int i=L[q];i<=r;i++)
				if(x[i]<=mid)++cnt;
			for(int i=p+1;i<q;i++)
				cnt+=get(i,mid);			
		}
		if(cnt>=k)
			ans=mid,RR=mid-1;
		else LL=mid+1;
	}
	return ans;
}
int main() {
	scanf("%d%d",&n,&T);
	for(int i=1;i<=n;i++)scanf("%d",&x[i]);
	sq=sqrt(n);
	for(int i=1;i<=sq;i++)L[i]=R[i-1]+1,R[i]=i*sq;
	if(R[sq]<n)++sq,L[sq]=R[sq-1]+1,R[sq]=n;
	for(int i=1;i<=sq;i++) {
		for(int j=L[i];j<=R[i];j++)
			id[j]=i;
		bui(i);
	}
	for(int l,r,k;T--;) {
		scanf("%s",op);
		if(op[0]=='C') {
			scanf("%d%d",&l,&r);
			mdy(l,r);
		} else {
			scanf("%d%d%d",&l,&r,&k);
			printf("%d\n",Ask(l,r,k));
		}
	}
}

代替平衡树

来一波刺激的:代替树套树

二逼平衡树

如果上面两题都没问题了,这题只需要时间

排名、前驱后继都是块内二分,修改和第 \(K\) 大同上

调了一个晚修。。。

#include<bits/stdc++.h>
#define fo(i,a,b) for(register int i=(a);i<=(b);i++)
using namespace std;
char buf[100000],*S=buf,*T=buf;
inline char G() { return S==T&&(T=(S=buf)+fread(buf,1,100000,stdin),S==T)?EOF:*S++; }
inline int Rd() {
	register int x=0; char C=G();
	for(;C<'0'||C>'9';C=G());
	for(;C>'/'&&C<':';C=G())x=(x<<1)+(x<<3)+(C^48);
	return x;
}
inline void Wr(int x) { if(x>9)Wr(x/10); putchar(x%10+48); }
inline void Out(int x) { if(x<0)putchar(45),x=-x; Wr(x),putchar(10); }
const int N=50005; int n,TT,x[N],y[N],id[N],L[N],R[N],sq,op,l,r,k;
inline void bui(int p) { fo(i,L[p],R[p])y[i]=x[i]; sort(y+L[p],y+R[p]+1); }
inline void mdy(int p,int v) { x[p]=v,bui(id[p]); }
inline int low(int p,int v) {
	register int LL=L[p],RR=R[p],mid,ans=L[p]-1;
	while(LL<=RR) {
		mid=LL+RR>>1;
		if(y[mid]<v)ans=mid,LL=mid+1;
		else RR=mid-1;
	} return ans-L[p]+1;
}
inline int get(int p,int v) {
	register int LL=L[p],RR=R[p],mid,ans=L[p]-1;
	while(LL<=RR) {
		mid=LL+RR>>1;
		if(y[mid]<=v)ans=mid,LL=mid+1;
		else RR=mid-1;
	} return ans-L[p]+1;
}
inline int rnk() {
	register int p=id[l],q=id[r],res=1;
	if(p==q) {
		fo(i,l,r)res+=(x[i]<k);
		return res;
	}
	fo(i,l,R[p])res+=(x[i]<k);
	fo(i,L[q],r)res+=(x[i]<k);
	fo(i,p+1,q-1)res+=low(i,k);
	return res;
}
inline int kth() {
	register int LL=0,RR=1e8,mid,ans=1,p=id[l],q=id[r],cnt;
	while(LL<=RR) {
		mid=LL+RR>>1,cnt=0;
		if(p==q) {
			fo(i,l,r)cnt+=(x[i]<=mid);
		} else {
			fo(i,l,R[p])cnt+=(x[i]<=mid);
			fo(i,L[q],r)cnt+=(x[i]<=mid);
			fo(i,p+1,q-1)cnt+=get(i,mid);	
		}
		if(cnt>=k)ans=mid,RR=mid-1;
		else LL=mid+1;
	}
	return ans;
}
inline int pre() {
	register int p=id[l],q=id[r],ans=-2147483647,LL,RR,mid,res;
	if(p==q) {
		fo(i,l,r)if(x[i]<k)ans=max(ans,x[i]);
		return ans;
	}
	fo(i,l,R[p])if(x[i]<k)ans=max(ans,x[i]);
	fo(i,L[q],r)if(x[i]<k)ans=max(ans,x[i]);
	fo(i,p+1,q-1) {
		LL=L[i],RR=R[i],res=-1;
		while(LL<=RR) {
			mid=LL+RR>>1;
			if(y[mid]<k)res=mid,LL=mid+1;
			else RR=mid-1;
		}
		if(~res && y[res]<k)ans=max(ans,y[res]);
	}
	return ans;
}
inline int suc() {
	register int p=id[l],q=id[r],ans=2147483647,LL,RR,mid,res;
	if(p==q) {
		fo(i,l,r)if(x[i]>k)ans=min(ans,x[i]);
		return ans;
	}
	fo(i,l,R[p])if(x[i]>k)ans=min(ans,x[i]);
	fo(i,L[q],r)if(x[i]>k)ans=min(ans,x[i]);
	fo(i,p+1,q-1) {
		LL=L[i],RR=R[i],res=-1;
		while(LL<=RR) {
			mid=LL+RR>>1;
			if(y[mid]>k)res=mid,RR=mid-1;
			else LL=mid+1;
		}
		if(~res && y[res]>k)ans=min(ans,y[res]);		
	}
	return ans;
}
int main() {
	n=Rd(),TT=Rd(),sq=sqrt(n);
	fo(i,1,n)x[i]=Rd();
	fo(i,1,sq)L[i]=R[i-1]+1,R[i]=i*sq;
	if(R[sq]<n)++sq,L[sq]=R[sq-1]+1,R[sq]=n;
	fo(i,1,sq) {
		fo(j,L[i],R[i])id[j]=i;
		bui(i);
	}
	while(TT--) {
		op=Rd(),l=Rd(),r=Rd();
		if(op^3)k=Rd();
		if(op==1)Out(rnk());
		else if(op==2)Out(kth()); 
		else if(op==3)mdy(l,r);
		else if(op==4)Out(pre());
		else if(op==5)Out(suc());
	}
} 

总结

分块虽然暴力,却是一个不错的数据结构

此外,如果数据够小,一些规律题可以用上述方法打表

这就是分块打表,复杂度 \(O(\sqrt n)\)

更重要的是如何运用

posted @ 2021-04-22 20:17  小蒟蒻laf  阅读(153)  评论(1编辑  收藏  举报