[知识学习] 分块

贴几个板子,供自己看


分块核心:暴力操作

把一串序列分成\(\sqrt{n}\)个块,对每个块操作

左端不完整块,右端不完整块暴力计算

中间完整块lazy标记


最简单的分块题:

数列分块入门1

#include <bits/stdc++.h>
#define N (100000+5)
using namespace std;
int n,k,a[N],lazy[N],pos[N],L[N],R[N];//a原数组,lazy懒标记,pos[i]:i对应的块,L[i]:块i的左端点,R[i]:块i的右端点
void add(int l,int r,int c){//区间加
	for(int i=l;i<=min(R[pos[l]],r);i++){//左端不完整区间处理
		a[i]+=c;
	}
	if(pos[l]!=pos[r]){
		for(int i=L[pos[r]];i<=r;i++){//右端不完整区间处理
			a[i]+=c;
		}
	}
	for(int i=pos[l]+1;i<pos[r];i++) lazy[i]+=c;//中间完整区间处理
}
int main(){
	scanf("%d",&n);
	k=sqrt(n);//每k个数分1块
	int sum=n/k+(n%k!=0);//共分sum个块
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) pos[i]=(i-1)/k+1;//每个数在哪个块
	for(int i=1;i<=sum;i++) L[i]=(i-1)*k+1,R[i]=i*k;//左右端点
	R[sum]=n;
	for(int i=1;i<=n;i++){
		int opt,l,r,c;
		scanf("%d%d%d%d",&opt,&l,&r,&c);
		if(opt==0) add(l,r,c);
		else if(opt==1) printf("%d\n",a[r]+lazy[pos[r]]);
	}
	return 0;
}


支持区间加,查找区间内前驱

可以利用C++STL里的set维护每个块,查lower_bound

每次修改时先把块里的删掉,修改后在加进来

数列分块入门3

#include <bits/stdc++.h>
#define N (100000+5)
using namespace std;
int n,k,a[N],lazy[N],pos[N],L[N],R[N];
set<int> t[N];
void add(int l,int r,int c){//同上
	for(int i=l;i<=min(R[pos[l]],r);i++){
		t[pos[l]].erase(a[i]);//先删掉原始的
		a[i]+=c;//更改
		t[pos[l]].insert(a[i]);//再插入新的
	}
	if(pos[l]!=pos[r]){
		for(int i=L[pos[r]];i<=r;i++){
			t[pos[r]].erase(a[i]);
			a[i]+=c;
			t[pos[r]].insert(a[i]);
		}
	}
	for(int i=pos[l]+1;i<pos[r];i++) lazy[i]+=c;
}
int pre(int l,int r,int c){//查询前驱,用set维护
	int ans=-1;
	for(int i=l;i<=min(R[pos[l]],r);i++)//统计左端不完整区间
		if(a[i]+lazy[pos[l]]<c) ans=max(ans,a[i]+lazy[pos[l]]);
	if(pos[l]!=pos[r]){
		for(int i=L[pos[r]];i<=r;i++)//统计右端不完整区间
			if(a[i]+lazy[pos[r]]<c) ans=max(ans,a[i]+lazy[pos[r]]);
	}
	for(int i=pos[l]+1;i<pos[r];i++){//用STL找中间的最大值
		int k=c-lazy[i];
		set<int>::iterator it=t[i].lower_bound(k);
		if(it==t[i].begin()) continue;
		it--;
		ans=max(ans,*it+lazy[i]);
	}
	return ans;
}
int main(){
	scanf("%d",&n);
	k=sqrt(n);
	int sum=n/k+(n%k!=0);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) pos[i]=(i-1)/k+1,t[pos[i]].insert(a[i]);
	for(int i=1;i<=sum;i++) L[i]=(i-1)*k+1,R[i]=i*k;
	R[sum]=n;
	for(int i=1;i<=n;i++){
		int opt,l,r,c;
		scanf("%d%d%d%d",&opt,&l,&r,&c);
		if(opt==0) add(l,r,c);
		else if(opt==1) printf("%d\n",pre(l,r,c));
	}
	return 0;
}

支持区间加,区间求和

思路大同小异,这里不细说,依然是老套路

数列分块入门4

#include <bits/stdc++.h>
#define N (100000+5)
#define int long long//开long long
using namespace std;
int n,k,b,a[N],pos[N],lazy[N],sum[N],L[N],R[N];
void add(int l,int r,int c){//区间加操作,不解释了
	for(int i=l;i<=min(R[pos[l]],r);i++) a[i]+=c,sum[pos[l]]+=c;
	if(pos[l]!=pos[r]){
		for(int i=L[pos[r]];i<=r;i++) a[i]+=c,sum[pos[r]]+=c;
	}
	for(int i=pos[l]+1;i<pos[r];i++) lazy[i]+=c,sum[i]+=c*k;
}
int getsum(int l,int r,int c){//区间求和操作,一样的道理
	int ans=0;
	for(int i=l;i<=min(R[pos[l]],r);i++) ans+=a[i]+lazy[pos[l]],ans%=(c+1);
	if(pos[l]!=pos[r]){
		for(int i=L[pos[r]];i<=r;i++) ans+=a[i]+lazy[pos[r]],ans%=(c+1);
	} 
	for(int i=pos[l]+1;i<pos[r];i++) ans+=sum[i],ans%=(c+1);
	return ans%(c+1);
}
signed main(){
	scanf("%lld",&n);
	k=sqrt(n);
	b=(n/k)+(n%k!=0);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		pos[i]=(i-1)/k+1;
		sum[pos[i]]+=a[i];
	}
	for(int i=1;i<=b;i++){
		L[i]=(i-1)*k+1,R[i]=i*k;
	}
	R[b]=n;
	for(int i=1;i<=n;i++){
		int opt,l,r,c;
		scanf("%lld%lld%lld%lld",&opt,&l,&r,&c);
		if(opt==0) add(l,r,c);
		else if(opt==1) printf("%lld\n",getsum(l,r,c));
	}
	return 0;
}

支持区间开方,区间求和

开方:最终得到的数都会是0或1(向下取整了)

所以分块维护,当哪个块里的数全部都是0或1是,再怎么开方都不会变了

所以用vis数组记录一下那些块已经只有0或1,就可以大大节约复杂度

数列分块入门5

#include <bits/stdc++.h>
#define N (1000000+5)
#define int long long
using namespace std;
int n,bl,k,a[N],sum[N],pos[N],L[N],R[N];
bool vis[N];
void solve(int x){//解决完整块开方的函数
	if(vis[x]) return;//这个块全部都是0或1,直接跳出不用开方
	vis[x]=1;
	for(int i=L[x];i<=R[x];i++){
		sum[x]-=a[i];//减去
		a[i]=sqrt(a[i]);//开方
		sum[x]+=a[i];//再加上
		if(a[i]>1) vis[x]=0;
	}
}
void add(int l,int r,int c){//不完整块开方函数,同理
	if(!vis[pos[l]]){
		for(int i=l;i<=min(R[pos[l]],r);i++){
			sum[pos[l]]-=a[i];
			a[i]=sqrt(a[i]);
			sum[pos[l]]+=a[i];
		}
		vis[pos[l]]=1;
		for(int i=L[pos[l]];i<=R[pos[l]];i++){
			if(a[i]>1){
				vis[pos[l]]=0;
				break;
			}
		}
	}
	if(!vis[pos[r]]&&pos[l]!=pos[r]){
		for(int i=L[pos[r]];i<=r;i++){
			sum[pos[r]]-=a[i];
			a[i]=sqrt(a[i]);
			sum[pos[r]]+=a[i];
		}
		vis[pos[r]]=1;
		for(int i=L[pos[r]];i<=R[pos[r]];i++){
			if(a[i]>1){
				vis[pos[r]]=0;
				break;
			}
		}
	}
	for(int i=pos[l]+1;i<pos[r];i++) solve(i);
}
int getsum(int l,int r,int c){
	int ans=0;
	for(int i=l;i<=min(R[pos[l]],r);i++) ans+=a[i]; 
	if(pos[l]!=pos[r])for(int i=L[pos[r]];i<=r;i++) ans+=a[i];
	for(int i=pos[l]+1;i<pos[r];i++) ans+=sum[i];
	return ans;
}
signed main(){
	scanf("%lld",&n);
	k=sqrt(n),bl=(n/k)+(n%k!=0);//同样
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		pos[i]=(i-1)/k+1;
		sum[pos[i]]+=a[i];
	}
	for(int i=1;i<=bl;i++){
		L[i]=(i-1)*k+1;
		R[i]=i*k;
	}
	R[bl]=n;
	for(int i=1;i<=n;i++){
		int opt,l,r,c;
		scanf("%lld%lld%lld%lld",&opt,&l,&r,&c);
		if(opt==0) add(l,r,c);
		else if(opt==1) printf("%lld\n",getsum(l,r,c));
	}
	return 0;
}

就到这里吧

posted @ 2020-01-14 17:27  Xx_queue  阅读(205)  评论(2编辑  收藏  举报