[浅谈]分块

2023 8.3

差不多花了小半天的时间来打分块,感觉理解起来并不难,但调起来是真的烦,调完却有脱胎换骨的感觉


分块

分块算法的核心思想是把一整个大区间分为若干个小区间,在对某一个区间进行修改、查询时,把这一个区间内整块的整体处理,零散的单独处理,以达到优化时间的目的。由于分块算法还是有一部分需要暴力去跑,所以分块算法又被称为“优雅的暴力”。

简称大段维护,全局朴素


分块

将序列分为若干个长度不超过 N \sqrt[]{N} N 的段。
记录每个段的左右端点,和每个值的所属块数。

区间操作

对于更改的 ( l , r ) (l,r) (l,r) 朴素更改 ( l , r l ) (l,r_l) (l,rl) ( l r , r ) (l_r,r) (lr,r) 的值。维护 ( l r + 1 , r l − 1 ) (l_r+1,r_l-1) (lr+1,rl1) 的值,用标记记录。

查询区间则像更改相同,还需要 前缀块和 数组。
单点查询则为 a i + c b j i a_i+c_{bj_i} ai+cbji c 数组为标记,bj为所属块。

code

数列分块入门 1

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e4+10;
int n,a[N],t,l[N],r[N],bj[N],c[N];
inline void add(int x,int y,int z){
	int p=bj[x],q=bj[y];
	if(p==q){
		for(int i=x;i<=y;++i) 
			a[i]+=z;
	}
	else{
		for(int i=x;i<=r[p];++i) a[i]+=z;
		for(int i=p+1;i<=q-1;++i) c[i]+=z;
		for(int i=l[q];i<=y;++i) a[i]+=z;
	}
	return ;
}
signed main(void){
	scanf("%lld",&n);
	t=sqrt(n);
	for(int i=1;i<=n;++i){
		scanf("%lld",a+i);
	}
	for(int i=1;i<=t;++i){
		l[i]=(i-1)*t+1;
		r[i]=i*t;
	}
	if(r[t]<n){
		++t;
		l[t]=r[t-1]+1;
		r[t]=n;
	}
	for(int i=1;i<=t;++i){
		for(int j=l[i];j<=r[i];++j){
			bj[j]=i;
		}
	}
	for(int opt,x,y,z,i=1;i<=n;++i){
		scanf("%lld%lld%lld%lld",&opt,&x,&y,&z);
		if(opt==0){
			add(x,y,z);
		}
		if(opt==1){//单点查询,原值+块值(块内每个点都+z) 
			printf("%lld\n",a[y]+c[bj[y]]);
		}
	}
	return 0;
}

数列分块入门 2

此题稍一变型,即为 教主的魔法

只是变为多少大于等于的了,用 ans+=len[i]-lower_bound(qq[i].begin(),qq[i].end(),o-c[i])-qq[i].begin() 即可。

当然,还有一些读入问题。

教主的魔法 的代码戳这里

#include<bits/stdc++.h>
using namespace std;
const int N=5e2+10;
int n,t,c[N],a[50010],l[N],r[N],bj[50010],len[N];
vector<int>qq[N];
inline int read(){
	int x=0;bool f=1;char s=getchar();
	while(s<'0'||s>'9'){if(s=='-')f=0;s=getchar();}
	while(s>='0'&&s<='9'){x=(x<<1)+(x<<3)+(s^48);s=getchar();}
	return f?x:-x;	
}
inline void write(int x) {
    if (x < 0) putchar('-'), x = -x;
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}
inline void uu(int x){
	for(int i=0;i<=len[x];++i)
		qq[x][i]=a[l[x]+i];
	sort(qq[x].begin(),qq[x].end());
}
inline void add(int x,int y,int z){
	int p=bj[x],q=bj[y];
	if(p==q){
		for(int i=x;i<=y;++i) 
			a[i]+=z;
		uu(bj[x]);
	}
	else{
		for(int i=x;i<=r[p];++i) a[i]+=z;
		uu(bj[x]);
		for(int i=p+1;i<=q-1;++i) c[i]+=z;
		for(int i=l[q];i<=y;++i) a[i]+=z;
		uu(bj[y]);
	}
	return ;
}
inline int ask(int x,int y,int z){
	int ans=0,p=bj[x],q=bj[y],o=z*z;
	if(p==q){
		for(int i=x;i<=y;++i){
			if(a[i]+c[p]<o){
				++ans;
			}
		}
	}
	else{
		for(int i=x;i<=r[p];++i){
			if(a[i]+c[p]<o){
				++ans;
			}
		}
		for(int i=p+1;i<=q-1;++i){
			ans+=lower_bound(qq[i].begin(),qq[i].end(),o-c[i])-qq[i].begin();
		}
		for(int i=l[q];i<=y;++i){
			if(a[i]+c[q]<o){
				++ans;
			}
		}
	}
	return ans;
}
signed main(void){
	n=read();t=sqrt(n);//重点,分为根号n块 ,写成了log2(N)调了很久 
	for(int i=1;i<=n;++i){
		a[i]=read();
	}
	for(int i=1;i<=t;++i){
		l[i]=r[i-1]+1;
		r[i]=i*t;
	} 
	if(r[t]<n){
		++t;
		l[t]=r[t-1]+1;
		r[t]=n;
	}
	for(int i=1;i<=t;++i){
		len[i]=r[i]-l[i]+1;
		for(int j=l[i];j<=r[i];++j){
			bj[j]=i;
			qq[i].push_back(a[j]);
		}
	}
	for(int i=1;i<=t;++i){
		sort(qq[i].begin(),qq[i].end());
	}
	for(int opt,x,y,z,o,i=1;i<=n;++i){
		opt=read();x=read();y=read();z=read();
		if(opt==0){
			add(x,y,z);
		}
		if(opt==1){
//			printf("%d\n",ask(x,y,z));
			write(ask(x,y,z));
			putchar('\n');
		}
	}
	return 0;
/*
4
1 2 2 3
0 1 3 1
1 1 3 2
1 1 4 1
1 2 3 2
*/ 
}

数列分块入门 3

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e2+10;
int n,t,c[N],a[100010],l[N],r[N],bj[100010],len[N];
vector<int>qq[N];
inline int read(){
	int x=0;bool f=1;char s=getchar();
	while(s<'0'||s>'9'){if(s=='-')f=0;s=getchar();}
	while(s>='0'&&s<='9'){x=(x<<1)+(x<<3)+(s^48);s=getchar();}
	return f?x:-x;	
}
inline void write(int x) {
    if (x < 0) putchar('-'), x = -x;
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}
inline void uu(int x){
	for(int i=0;i<=len[x];++i)
		qq[x][i]=a[l[x]+i];
	sort(qq[x].begin(),qq[x].end());
}
inline void add(int x,int y,int z){
	int p=bj[x],q=bj[y];
	if(p==q){
		for(int i=x;i<=y;++i) a[i]+=z;
		uu(bj[x]);
	}
	else{
		for(int i=x;i<=r[p];++i) a[i]+=z;
		uu(bj[x]);
		for(int i=p+1;i<=q-1;++i) c[i]+=z;
		for(int i=l[q];i<=y;++i) a[i]+=z;
		uu(bj[y]);
	}
	return ;
}
inline int ask(int x,int y,int z){
	int ans=-1,p=bj[x],q=bj[y],k;
	if(p==q){
		for(int i=x;i<=y;++i){
			if(a[i]+c[p]<z){
				ans=max(ans,a[i]+c[p]);
			}
		}
	}
	else{
		for(int i=x;i<=r[p];++i){
			if(a[i]+c[p]<z){
				ans=max(ans,a[i]+c[p]);
			}
		}
		for(int i=p+1;i<=q-1;++i){
			k=lower_bound(qq[i].begin(),qq[i].end(),z-c[i])-qq[i].begin();//比它-标记值>=的第一个值 
			if(k) ans=max(ans,qq[i][k-1]+c[i]);
		}
		for(int i=l[q];i<=y;++i){
			if(a[i]+c[q]<z){
				ans=max(ans,a[i]+c[q]);
			}
		}
	}
	return ans;
}
signed main(void){
	n=read();t=sqrt(n);
	for(int i=1;i<=n;++i){
		a[i]=read();
	}
	for(int i=1;i<=t;++i){
		l[i]=r[i-1]+1;
		r[i]=i*t;
	} 
	if(r[t]<n){
		++t;
		l[t]=r[t-1]+1;
		r[t]=n;
	}
	for(int i=1;i<=t;++i){
		len[i]=r[i]-l[i]+1;
		for(int j=l[i];j<=r[i];++j){
			bj[j]=i;
			qq[i].push_back(a[j]);
		}
	}
	for(int i=1;i<=t;++i){
		sort(qq[i].begin(),qq[i].end());
	}
	for(int opt,x,y,z,i=1;i<=n;++i){
		opt=read();x=read();y=read();z=read();
		if(opt==0){
			add(x,y,z);
		}
		if(opt==1){
//			printf("%d\n",ask(x,y,z));
			write(ask(x,y,z));
			putchar('\n');
		}
	}
	return 0;
/*
4
1 2 2 3
0 1 3 1
1 1 3 2
1 1 4 1
1 2 3 2
*/ 
}

数列分块入门 4

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e4+10;
int n,a[N],t,l[N],r[N],bj[N],c[N],sum[N];
inline void init(){
	t=sqrt(n);
	for(int i=1;i<=t;++i){
		l[i]=(i-1)*t+1;
		r[i]=i*t;
	}
	if(r[t]<n){
		++t;
		l[t]=r[t-1]+1;
		r[t]=n;
	}
	for(int i=1;i<=t;++i){
		for(int j=l[i];j<=r[i];++j){
			bj[j]=i;
			sum[i]+=a[j];
		}
	}
}
inline void add(int x,int y,int z){
	int p=bj[x],q=bj[y];
	if(p==q){
		for(int i=x;i<=y;++i) 
			a[i]+=z;
		sum[p]+=z*(y-x+1);
	}
	else{
		for(int i=x;i<=r[p];++i) a[i]+=z;
		sum[p]+=z*(r[p]-x+1);
		for(int i=p+1;i<=q-1;++i) c[i]+=z;
		for(int i=l[q];i<=y;++i) a[i]+=z;
		sum[q]+=z*(y-l[q]+1); 
	}
	return ;
}
inline int ask(int x,int y){
	int p=bj[x],q=bj[y],ans=0;
	if(p==q){
		for(int i=x;i<=y;++i) ans+=a[i];
		ans+=c[p]*(y-x+1);
	}
	else{
		for(int i=x;i<=r[p];++i) ans+=a[i];
		ans+=c[p]*(r[p]-x+1);
		for(int i=p+1;i<=q-1;++i) ans+=sum[i]+c[i]*(r[i]-l[i]+1);
		for(int i=l[q];i<=y;++i) ans+=a[i];
		ans+=c[q]*(y-l[q]+1);
	}
	return ans;
}
signed main(void){
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d",a+i);
	init();
	for(int opt,x,y,z,i=1;i<=n;++i){
		scanf("%lld%lld%lld%lld",&opt,&x,&y,&z);
		if(opt==0){
			add(x,y,z);
		}
		if(opt==1){
			printf("%lld\n",(ask(x,y)%(z+1)));
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值