【c++】 分块入门知识

笔记:分块入门

分块的作用

像线段树一样,是一个数据结构,用于处理数列操作等问题。它跟线段树比起来,速度会慢一点,但是可以解决的问题更多,也更好理解(至少我是这么觉得的)。
它将数组分成好多块,每一次操作的复杂度为 O ( n ) O(\sqrt{n}) O(n ) 其实就比线段树 O ( log ⁡ n ) O(\log n) O(logn) 慢一点,却被人称为“暴力”(一点也不暴力的好吗!!!)。

分块的思想

分块,是将数组分成 N \sqrt{N} N 块,每次操作先暴力修改两边的不完整的块(散块),再批量修改中间的完整的块(整块)。这样子就可以将每一次操作的时间压缩到根号级的了。

模板:初始化分块

在这里需要维护几个东西:每一个块的起始,末尾位置,长度,数组中每一个数属于哪个块,每个块长,以及块的数量。其中 tag 和 sum 比较重要,tag代表每一个块被区间加了多少(作为整块),在以后还需要用到标记下传,也就是将 tag 加到原数组上,sum 代表每一个块的总和(也可能是状态总和)

int a[50005],bl[50005],st[1005],en[1005],len[50005],tag[1005],sum[1005],blocknum,blocklen;
/*a(原数组),bl(属于哪一个块),st(块的起始位),en(块的终点位),len(块的长度),tag(操作用,代表每块修改时的标记),sum(每一个块的总和),blocknum(块的数量),blocklen(每一个分块的长度)*/
void init() {
	blocklen=sqrt(n);
	if(n%blocklen>0) blocklen+1;
	else blocknum=blocklen;
	for(int i=1;i<=blocknum;i++) {
		st[i]=(i-1)*blocklen+1;//由于blocklen是块长,所以st是这样计算的
		en[i]=i*blocklen;
	}
	en[blocknum]=n;//最后一个块特殊处理
	for(int i=1;i<=blocknum;i++) len[i]=en[i]-st[i]+1;//取长度
	for(int i=1;i<=blocknum;i++)
		for(int j=st[i];j<=en[i];j++)
			bl[j]=i,sum[i]+=a[j];//划分区域,计算初始的块和
}

模板:区修单查

这里的典型题目是分块入门1,运用分块求加法,其实比线段树好写多了,首先给每一个分块进行加操作,假如 l l l r r r 在同一个分块内,直接相加,不然分三部分考虑,起始,中间和末尾。两个散块直接暴力,中间整块统一计算到 tag 里面,查询时再加上。
由于只涉及到单点查询,不需要记总和。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int q,n;
int a[50005],bl[50005],st[1005],en[1005],len[1005],tag[50005],blocknum,blocklen;
void init() {
	blocklen=sqrt(n);
	if(n%blocklen>0) blocklen+1;
	else blocknum=blocklen;
	for(int i=1;i<=blocknum;i++) {
		st[i]=(i-1)*blocklen+1;
		en[i]=i*blocklen;
	}
	en[blocknum]=n;
	for(int i=1;i<=blocknum;i++) len[i]=en[i]-st[i]+1;
	for(int i=1;i<=blocknum;i++)
		for(int j=st[i];j<=en[i];j++)
			bl[j]=i;
}
signed main(){
	scanf("%lld",&n);
	q=n;
	init();
	for(int i=1;i<=n;i++) {
		scanf("%lld",&a[i]);
	}
	while(q--) {
		int opt,l,r,x;
		scanf("%lld%lld%lld%lld",&opt,&l,&r,&x);
		if(opt==0) {
			if(bl[l]==bl[r]) {//在同一个块内
				for(int i=l;i<=r;i++) a[i]+=x;
			}
			else {
				for(int i=l;i<=en[bl[l]];i++) a[i]+=x;//最开始的块 
				for(int i=bl[l]+1;i<=bl[r]-1;i++) tag[i]+=x;//中间的块 
				for(int i=st[bl[r]];i<=r;i++) a[i]+=x;//最后的块 
			}
		}
		else printf("%lld\n",a[r]+tag[r]);
	}
	return 0;
}

模板:区修区查

这里的典型题目是洛谷P2357:守墓人,这是一道涉及到区间查询的题目。注意了,有于区间可能很大,要用一个数组记录每一个分块的总和,散块暴力的跑一遍记下,整块直接用和来求。注意修改时要改变总和。单点简单,可以看作是小范围的区间。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int q,n;
int a[200005],bl[200005],st[2005],en[2005],len[2005],tag[2005],sum[2005],blocknum,blocklen;
inline void init() {
	blocklen=sqrt(n);
	if(n%blocklen>0) blocknum=blocklen+1;
	else blocknum=blocklen;
	for(register int i=1;i<=blocknum;i++) {
		st[i]=(i-1)*blocklen+1;
		en[i]=i*blocklen;
	}
	en[blocknum]=n;
	for(register int i=1;i<=blocknum;i++) len[i]=en[i]-st[i]+1;
	for(register int i=1;i<=blocknum;i++)
		for(int j=st[i];j<=en[i];j++)
			bl[j]=i,sum[i]+=a[j];
}
inline int read() {
	int x=0; bool y=false;
	char ch=getchar();
	while(ch<'0' || ch>'9') y=(ch=='-'),ch=getchar();
	while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^'0'), ch=getchar();
	return y?-x:x;
}
signed main(){
	n=read(),q=read();
	for(register int i=1;i<=n;i++) {
		a[i]=read();
	}
	init();
	while(q--) {
		int opt,l,r,x;
		opt=read();
		if(opt==1) {
			l=read(),r=read(),x=read();
			//printf("%lld %lld\n",bl[l],bl[r]);
			if(bl[l]==bl[r]) {//在同一个块内
				for(register int i=l;i<=r;i++) a[i]+=x,sum[bl[l]]+=x;
			}
			else {
				for(register int i=l;i<=en[bl[l]];i++) a[i]+=x;//最开始的块
				sum[bl[l]]+=(en[bl[l]]-l+1)*x;
				for(register int i=bl[l]+1;i<=bl[r]-1;i++) tag[i]+=x,sum[i]+=len[i]*x;//中间的块
				for(register int i=st[bl[r]];i<=r;i++) a[i]+=x;//最后的块
				sum[bl[r]]+=(r-st[bl[r]]+1)*x;
			}
		}
		else if(opt==2){
			x=read();
			a[1]+=x;
			sum[1]+=x;
		}
		else if(opt==3) {
			x=read();
			a[1]-=x;
			sum[1]-=x;
		}
		else if(opt==4) {
			l=read(),r=read();
			int ans=0;
			if(bl[l]==bl[r]) {//在同一个块内
				for(register int i=l;i<=r;i++) ans+=a[i];
				ans+=(r-l+1)*add[bl[l]];
			}
			else {
				for(register int i=l;i<=en[bl[l]];i++) ans+=a[i]+tag[bl[i]];//最开始的块
				//printf("%lld\n",ans);
				for(register int i=st[bl[r]];i<=r;i++) ans+=a[i]+tag[bl[i]];//最后的块
				//printf("%lld\n",ans);
				for(register int i=bl[l]+1;i<=bl[r]-1;i++) ans+=sum[i];//中间的块
			}
			
			printf("%lld\n",ans);
		}
		else printf("%lld\n",a[1]+tag[1]);
	}
	return 0;
}

好题推荐

p3373线段树2|p5356由乃打扑克|题单
最后祝大家学习愉快!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值