分块专题

分块专题

Problem A. \(O(\sqrt{n})-O(1)\) 区间加,区间求和

维护每个块内元素总和 \(S_i\),以及每个元素至其所在块的左端点/右端点的区间内元素和 \(ls_i,rs_i\)

修改时,整块打 tag,散块暴力,每次对整块重新维护前缀和,复杂度为 \(O(\sqrt{n})\)

查询时,整块和散块都能做到 \(O(1)\) 查询。

Problem B. \(O(1)-O(\sqrt{n})\) 区间加,区间求和

修改时,整块在左右两个块上打两个差分标记,散块在块内打差分标记并维护每块的和,复杂度 \(O(1)\)

查询时,把所有整块和两个散块扫一遍,计算差分标记的影响并清除差分标记,计算答案,复杂度 \(O(\sqrt{n})\)

Problem C. \(O(\sqrt{n})-O(1)\) 单点插入,求全局第 \(k\) 大,值域线性

对值域分块,转化为问题 A。

Problem D. \(O(\sqrt{n})-O(1)\) 单点插入,求全局第 \(k\) 大,值域较大

我们对已经插入进来的数字的集合 \(S\) 分块,从小到大每 \(\sqrt{n}\) 个元素为一块。

这种分块方式已经可以做到 \(O(1)\) 查询。

插入 \(v\) 时,找到 \(v\) 应在的块,将其插入。若这个块的大小大于了 \(\sqrt{n}\),那么就把最后一个元素扔进下一个块,然后再对下一个块重复上述过程。复杂度 \(O(\sqrt{n})\)

Problem E. P2801 教主的魔法

区间加,区间查询 \(\geq C\) 的数。 \(n\leq 10^6,Q\leq 3000,C\leq 10^9\)

对序列分块,设每块大小为 \(B\)

我们对每一块内的元素分别从小到大排序,查询时二分即可。

修改时,整块打 tag,散块重构,\(O(\frac n B+B\log n)\)

查询时,散块暴力,整块二分,\(O(B+\frac n B \log n)\)

\(B=\sqrt{n}\),总复杂度 \(O(Q\sqrt{n}\log n)\),已经可以通过。

但可以做到更优。对散块修改时,我们把被修改的一部分按照从小到大的顺序拿出来,和剩下的部分进行一次归并可以做到 \(O(B+\frac n B)\)

此时应取 \(B=\sqrt{n\log n}\),总时间复杂度 \(O(Q\sqrt{n\log n})\)

实际上可以继续将查询优化到线性,我还不会

int n,QQ,a[N];
int C,B,L[M],R[M],bl[N],b[N],tag[M]; 

bool Cmp1(int x,int y){
	return a[x]<a[y];
} 

int c[N],d[N],f[N],P,Q;

void Spread(int x){
	for(int i=L[x];i<=R[x];i++) a[i]+=tag[x];
	tag[x]=0;
}

void Merge(){
	int p=0,i=1,j=1;
	while(i<=P&&j<=Q){
		if(a[c[i]]<a[d[j]]) f[++p]=c[i++];
		else f[++p]=d[j++];
	}
	while(i<=P) f[++p]=c[i++];
	while(j<=Q) f[++p]=d[j++];
}

void ReBuild(int x,int l,int r,int v){               
	Spread(x); P=Q=0;
	for(int i=l;i<=r;i++) a[i]+=v;
	for(int i=L[x];i<=R[x];i++){
		if(l<=b[i]&&b[i]<=r) c[++P]=b[i];
		else d[++Q]=b[i];
	}
	Merge();
	for(int i=1;i<=P+Q;i++) b[i+L[x]-1]=f[i];
	return;
}

void Update(int l,int r,int v){
	int p=bl[l],q=bl[r];
	if(p==q) return ReBuild(p,l,r,v);
	for(int i=p+1;i<=q-1;i++) tag[i]+=v;
	ReBuild(p,l,R[p],v);
	ReBuild(q,L[q],r,v);
}

int Ask(int l,int r,int v){
	int p=bl[l],q=bl[r],res=0;
	if(p==q){
		for(int i=l;i<=r;i++)
			res+=a[i]+tag[p]>=v;
		return res;
	}
	for(int i=p+1;i<=q-1;i++){
		int pl=L[i],pr=R[i],pos=-1;
		while(pl<=pr){
			int mid=(pl+pr)>>1;
			if(a[b[mid]]+tag[i]>=v) pos=mid,pr=mid-1;
			else pl=mid+1;
		}
		if(pos!=-1) res+=R[i]-pos+1;
	}
	for(int i=l;i<=R[p];i++) res+=(a[i]+tag[p]>=v);
	for(int i=L[q];i<=r;i++) res+=(a[i]+tag[q]>=v);
	return res;
}
 
signed main(){
	read(n),read(QQ);
	for(int i=1;i<=n;i++) read(a[i]),b[i]=i;
	B=sqrt(n*log2(n));
	for(int i=1;i<=n;i+=B){
		C++; L[C]=i,R[C]=min(n,i+B-1);
		for(int j=L[C];j<=R[C];j++) bl[j]=C;
		sort(b+L[C],b+R[C]+1,Cmp1);
	}
	for(int i=1;i<=QQ;i++){
		char op[5]; scanf("%s",op);
		if(op[0]=='M'){
			int l,r,w;
			read(l),read(r),read(w);
			Update(l,r,w);
		}
		else{
			int l,r,w;
			read(l),read(r),read(w);
			printf("%d\n",Ask(l,r,w));
		}
	}
    return 0;
}

Problem F. P5356 由乃打扑克

区间加,区间第 \(k\) 大。\(n,Q\leq 10^5\),值域 \([-2\times 10^9,2\times 10^9]\)

设块长为 \(B\)

在每块内将元素从小到大排序。查询时,先二分第 \(k\) 大的值 \(v\),然后再查询区间内有多少元素 \(\leq v\)\(O((B+\frac n B\log n)\log V)\)

修改时,与 Problem E 类似,\(O(B+\frac n B)\)

\(B=\sqrt{n\log n}\),总复杂度 \(O(Q\sqrt{n\log n}\log V)\),无法通过。

考虑查询时将散块内涉及到的元素拿出来作为整块处理,复杂度降为 \(O(\frac n B \log n\log V+B)\)

\(B=\sqrt{n \log n\log V}\),做到了 \(O(Q\sqrt{n\log n\log V})\) 的总复杂度。

但由于两边常数的差异,\(B\)\(700\) 左右最优。

int n,Q,a[N],b[N];
int B,C,L[M],R[M],bl[N],tag[M];
int c[N],d[N],f[N],tc,td;

void Spread(int p){
	for(int i=L[p];i<=R[p];i++) a[i]+=tag[p];
	tag[p]=0;
}

void Rebuild(int p,int l,int r,int v){
	Spread(p); tc=td=0;
	for(int i=l;i<=r;i++) a[i]+=v; 
	for(int i=L[p];i<=R[p];i++){
		if(l<=b[i]&&b[i]<=r) c[++tc]=b[i];
		else d[++td]=b[i];
	}
	int x=0,i=1,j=1;
	while(i<=tc&&j<=td){
		if(a[c[i]]<a[d[j]]) f[++x]=c[i++];
		else f[++x]=d[j++];
	}
	while(i<=tc) f[++x]=c[i++];
	while(j<=td) f[++x]=d[j++];
	for(i=L[p];i<=R[p];i++) b[i]=f[i-L[p]+1];
}

bool Cmp(int x,int y){
	return a[x]<a[y];
}

void Update(int l,int r,int v){
	int p=bl[l],q=bl[r];
	if(p==q) return Rebuild(p,l,r,v);
	for(int i=p+1;i<=q-1;i++) tag[i]+=v;
	Rebuild(p,l,R[p],v),Rebuild(q,L[q],r,v);
}

int sl[N],sr[N],cl,cr;

int AskPos(int c[],int m,int k){
	int pl=1,pr=m,res=0;
	while(pl<=pr){
		int mid=(pl+pr)>>1;
		if(a[c[mid]]<=k) res=mid,pl=mid+1;
		else pr=mid-1;
	}
	return res;
}

int AskCnt(int p,int q,int v){
	int res=AskPos(sl,cl,v-tag[p])+AskPos(sr,cr,v-tag[q]);
	for(int i=p+1;i<=q-1;i++)
		res+=AskPos(b+L[i]-1,R[i]-L[i]+1,v-tag[i]);
	return res;
}

int AskVal(int l,int r,int w){
	if(r-l+1<w||w<1) return -1;
	int p=bl[l],q=bl[r]; cl=cr=0; 
	if(p==q){
		for(int i=L[p];i<=R[p];i++)
			if(l<=b[i]&&b[i]<=r) sl[++cl]=b[i];
		return a[sl[w]]+tag[p];
	}
	for(int i=L[p];i<=R[p];i++)
		if(b[i]>=l) sl[++cl]=b[i];
	for(int i=L[q];i<=R[q];i++)
		if(b[i]<=r) sr[++cr]=b[i];
	int vl=INT_MAX,vr=INT_MIN,res=0;
	Ckmin(vl,a[sl[1]]+tag[p]),Ckmin(vl,a[sr[1]]+tag[q]);
	Ckmax(vr,a[sl[cl]]+tag[p]),Ckmax(vr,a[sr[cr]]+tag[q]);
	for(int i=p+1;i<=q-1;i++){
		Ckmin(vl,a[b[L[i]]]+tag[i]);
		Ckmax(vr,a[b[R[i]]]+tag[i]);
	}
	while(vl<=vr){
		int mid=(1ll*vl+1ll*vr)>>1;
		if(AskCnt(p,q,mid)>=w) res=mid,vr=mid-1;
		else vl=mid+1;
	}
	return res;
}

signed main(){
	read(n),read(Q);
	for(int i=1;i<=n;i++) read(a[i]),b[i]=i;
	B=700;
	for(int i=1;i<=n;i+=B){
		C++; L[C]=i,R[C]=min(n,i+B-1);
		for(int j=L[C];j<=R[C];j++) bl[j]=C;
		sort(b+L[C],b+R[C]+1,Cmp);
	}
	while(Q--){
		int op,l,r,w;
		read(op),read(l),read(r),read(w);
		if(op==2) Update(l,r,w);
		else printf("%d\n",AskVal(l,r,w));
	}
    return 0;
}

Problem G. P3863 序列

区间加,查询一个位置之前有多少个时刻的值 \(\geq k\)\(n,Q\leq 10^5\)

转化为二维平面上的问题,序列轴为 \(x\) 轴,时间轴为 \(y\) 轴。

区间加对应着平面上 3-Side 矩形加,查询对应着一条平行于 \(y\) 轴的线段上有多少 \(\geq k\) 的元素。

考虑扫描线,用一条平行于 \(y\) 轴的线从左往右扫,分块维护直线上扫到的位置的值。

于是转化为区间加、区间 \(\geq k\) 个数问题,转化为 Problem E,\(O(n\sqrt{n\log n})\)

int n,m,k;
ll a[N];

struct Points{
	int x,y,id,v;
	
	bool operator<(const Points& tmp)const{
		if(x!=tmp.x) return x<tmp.x;
		else if(y!=tmp.y) return y<tmp.y;
		else return id<tmp.id;
	} 
}c[N*6];

int B,C,L[N],R[N],bl[N],b[N],ans[N],op[N];
ll tag[N];
int f[N],g[N],h[N],cf,cg,ch;

void Spread(int p){
	for(int i=L[p];i<=R[p];i++) a[i]+=tag[p];
	tag[p]=0;
}

void Rebuild(int p,int l,int r,int v){
	Spread(p);
	cf=cg=ch=0;
	for(int i=l;i<=r;i++) a[i]+=v; 
	for(int i=L[p];i<=R[p];i++){
		if(l<=b[i]&&b[i]<=r) f[++cf]=b[i];
		else g[++cg]=b[i];
	}
	int i=1,j=1;
	while(i<=cf&&j<=cg){
		if(a[f[i]]<a[g[j]]) h[++ch]=f[i++];
		else h[++ch]=g[j++];
	}
	while(i<=cf) h[++ch]=f[i++];
	while(j<=cg) h[++ch]=g[j++];
	for(i=L[p];i<=R[p];i++) b[i]=h[i-L[p]+1];
}

void Update(int x,int v){
	int p=bl[x];
	if(p==C) return Rebuild(p,x,m,v);
	for(int i=p+1;i<=C;i++) tag[i]+=v;
	Rebuild(p,x,R[p],v);
}

int Ask(int x,int v){
	int p=bl[x],res=0;
	if(p==1){
		for(int i=0;i<=x;i++)
			res+=(a[i]+tag[p]>=v);
		return res;
	}
	for(int i=1;i<p;i++){
		int l=L[i],r=R[i],pos=-1;
		while(l<=r){
			int mid=(l+r)>>1;
			if(a[b[mid]]+tag[i]>=v) r=mid-1,pos=mid;
			else l=mid+1;
		}
		if(pos!=-1) res+=R[i]-pos+1;
	}
	for(int i=L[p];i<=x;i++) res+=(a[i]+tag[p]>=v);
	return res;
}

signed main(){
	read(n),read(m);
	for(int i=1;i<=n;i++){
		int x; read(x);
		c[++k]={i,0,0,x};
		c[++k]={i+1,0,0,-x};
	}
	for(int i=0;i<=m;i++) b[i]=i;
	B=sqrt(m*log2(m));
	for(int i=0;i<=m;i+=B){
		C++; L[C]=i,R[C]=min(m,i+B-1);
		for(int j=L[C];j<=R[C];j++) bl[j]=C;
	}
	for(int i=1;i<=m;i++){
		read(op[i]);
		if(op[i]==1){
			int l,r,x;
			read(l),read(r),read(x);
			//{x,y,id,v} 
			c[++k]={l,i,0,x};
			c[++k]={r+1,i,0,-x};
		}
		else{
			int x,y; read(x),read(y);
			c[++k]={x,i-1,i,y};
		}
	}
	sort(c+1,c+k+1);
	for(int i=1;i<=k;i++){
		if(c[i].id==0) Update(c[i].y,c[i].v);
		else ans[c[i].id]=Ask(c[i].y,c[i].v); 
	}
	for(int i=1;i<=m;i++)
		if(op[i]==2) printf("%d\n",ans[i]);
    return 0;
}

posted @ 2025-05-23 17:58  XP3301_Pipi  阅读(18)  评论(0)    收藏  举报
Title