【学习笔记】分块学习笔记

学习笔记索引

分块经常听别人提起,我也学一下。

正片

分块就是将一个数列分成很多块,然后每块单独操作,最后的结果放到原数列里。

分块的题目类型经常是区间中修改和查询。

这里,一个长度为 n 的数列最多分成 n 个块。

先来看例题吧。

例题

P2357 守墓人

题目背景

在一个荒凉的墓地上,有一个令人尊敬的守墓人, 他看守的墓地从来没有被盗过, 所以人们很放心的把自己的先人的墓安顿在他那

守墓人能看好这片墓地是必然而不是偶然……

因为……守墓人懂风水 0.0

题目描述

他把墓地分为主要墓碑和次要墓碑, 主要墓碑只能有 1 个, 守墓人把他记为 1 号, 而次要墓碑有 n1 个,守墓人将之编号为 2,3n,所以构成了一个有 n 个墓碑的墓地。

而每个墓碑有一个初始的风水值,这些风水值决定了墓地的风水的好坏,所以守墓人需要经常来查询这些墓碑。

善于运用风水的守墓人,通过一次次逆天改命,使得自己拥有了无限寿命,没人知道他活了多久。这天,你幸运的拜访到了他,他要求你和他共同见证接下来几年他的战果,但不过他每次统计风水值之和都需要你来帮他计算,算错了他会要你命 QAQ

风水也不是不可变,除非遭遇特殊情况,已知在接下来的 2147483647 年里,会有 f 次灾难,守墓人会有几个操作:

  1. [l,r] 这个区间所有的墓碑的风水值增加 k

2.将主墓碑的风水值增加 k

3.将主墓碑的风水值减少 k

4.统计 [l,r] 这个区间所有的墓碑的风水值之和

5.求主墓碑的风水值

上面也说了,很多人会把先人的墓安居在这里,而且守墓人活了很多世纪→_→,墓碑的数量会多的你不敢相信= =

守墓人和善的邀请你帮他完成这些操作,要不然哪天你的旅馆爆炸了,天上下刀子.....

为了活命,还是帮他吧

输入格式

第一行,两个正整数 n,f 表示共有 n 块墓碑,并且在接下来的 2147483647年里,会有 f 次世界末日

第二行,n 个正整数,表示第 i 块墓碑的风水值

接下来 f 行,每行都会有一个针对世界末日的解决方案,如题所述,标记同题

输出格式

输出会有若干行,对 45 的提问做出回答

提示

20% 的数据满足:1n100

50% 的数据满足:1n6000

100% 的数据满足:1n,f2×105,答案不超过 64 位整数。


这道题可以用线段树,但是我用分块做。

主墓碑的修改和查询很简单,我们先看区间的修改和查询。

其实在修改的时候,我们可以发现:

1.这个区间中会有一些完整的块,我们就可以不用修改 ai,给他打上修改的标记就行了,类似于懒标记,再将这个块中 a 的总和加上风水值 × 区间长度,即为每一项都加上了风水值 。

2.区间中的两头也会有不完整的块,我们直接遍历这个块,暴力修改就行了,但是要记得改掉记录的这个块中 a 的总和。

再来看看查询,仍然是有两种块:

1.如果是完整块,直接将存总和的变量加上这个块中 a 的总和(有记录)就可以了。

2.如果是不完整块,需要加上目前的 ai 和历史上标记的增加的值。

还是挺简单的吧!

CODE:

#include<bits/stdc++.h>
using namespace std;
long long n,m,k,l,r,y,id[200010],siz,a[200010],b[200010],sum[200010];
void updata(long long l,long long r,long long y){
	long long s=id[l],t=id[r];//开头在的块和结尾在的块
	if(s==t){//全在一个块
		for(int i=l;i<=r;i++){//暴力修改
			a[i]+=y,sum[s]+=y;//sum是和,别忘了!
		}return;
	}for(int i=l;id[i]==s;i++){
		a[i]+=y,sum[s]+=y;
	}for(int i=s+1;i<t;i++){
		b[i]+=y,sum[i]+=y*siz;//打标记
	}for(int i=r;id[i]==t;i--){
		a[i]+=y,sum[t]+=y;
	}
}long long query(long long l,long long r){
	long long s=id[l],t=id[r],ret=0;
	if(s==t){
		for(int i=l;i<=r;i++){
			ret+=a[i],ret+=b[s];//加上a[i]和标记
		}return ret;
	}for(int i=l;id[i]==s;i++){
		ret+=a[i],ret+=b[s];
	}for(int i=s+1;i<t;i++){
		ret+=sum[i];//加上整个块的和
	}for(int i=r;id[i]==t;i--){
		ret+=a[i],ret+=b[t];
	}return ret;
}int main(){
	scanf("%lld%lld",&n,&m);
	siz=sqrt(n);//算长度
	for(int i=1;i<=n;i++){//分块
		scanf("%lld",&a[i]);
		id[i]=(i-1)/siz+1;
		sum[(i-1)/siz+1]+=a[i];
	}for(int i=1;i<=m;i++){
		scanf("%lld",&k);
		if(k==1){
			scanf("%lld%lld%lld",&l,&r,&y);
			updata(l,r,y);//修改
		}else if(k==2){
			scanf("%lld",&y);
			a[1]+=y,sum[1]+=y;//单点直接修改
		}else if(k==3){
			scanf("%lld",&y);
			a[1]-=y,sum[1]-=y;
		}else if(k==4){
			scanf("%lld%lld",&l,&r);
			printf("%lld\n",query(l,r));//查询
		}else{
			printf("%lld\n",a[1]+b[1]);//别忘了加上历史标记!
		}
	}return 0;
}

再看一道题。

P4145 上帝造题的七分钟 2 / 花神游历各国

题目背景

XLk 觉得《上帝造题的七分钟》不太过瘾,于是有了第二部。

题目描述

"第一分钟,X 说,要有数列,于是便给定了一个正整数数列。

第二分钟,L 说,要能修改,于是便有了对一段数中每个数都开平方(下取整)的操作。

第三分钟,k 说,要能查询,于是便有了求一段数的和的操作。

第四分钟,彩虹喵说,要是 noip 难度,于是便有了数据范围。

第五分钟,诗人说,要有韵律,于是便有了时间限制和内存限制。

第六分钟,和雪说,要省点事,于是便有了保证运算过程中及最终结果均不超过 64 位有符号整数类型的表示范围的限制。

第七分钟,这道题终于造完了,然而,造题的神牛们再也不想写这道题的程序了。"

——《上帝造题的七分钟·第二部》

所以这个神圣的任务就交给你了。

输入格式

第一行一个整数 n,代表数列中数的个数。

第二行 n 个正整数,表示初始状态下数列中的数。

第三行一个整数 m,表示有 m 次操作。

接下来 m 行每行三个整数 k l r

  • k=0 表示给 [l,r] 中的每个数开平方(下取整)。

  • k=1 表示询问 [l,r] 中各个数的和。

数据中有可能 l>r,所以遇到这种情况请交换 lr

输出格式

对于询问操作,每行输出一个回答。

提示

对于 30% 的数据,1n,m103,数列中的数不超过 32767

对于 100% 的数据,1n,m1051l,rn,数列中的数大于 0,且不超过 1012


这题也是分块,但是要有一个小优化。

我们可以发现,1 就是 1,而且一个数开根号很快就会变为 1,所以,我们可以判断是否全是 1,如果是,修改就不用了,查询直接加上长度就行了。

那么如何判断是否全是 1 呢?直接判断它的最大值是不是 1 就可以了。

CODE:

#include<bits/stdc++.h>
using namespace std;
long long n,m,k,l,r,id[100010],siz,a[100010],b[100010],sum[100010];
void updata(long long l,long long r){
	long long s=id[l],t=id[r];
	if(s==t){
		sum[s]=0,b[s]=0;
		for(int i=l;i<=r;i++){
			a[i]=(long long)sqrt(a[i]);//开根
		}for(int i=siz*(s-1)+1;i<=siz*s;i++){
			b[s]=max(b[s],a[i]);//更新
			sum[s]+=a[i];
		}return;
	}sum[s]=0,b[s]=0;
	for(int i=l;id[i]==s;i++){
		a[i]=(long long)sqrt(a[i]);
	}for(int i=siz*(s-1)+1;i<=siz*s;i++){
		b[s]=max(b[s],a[i]);
		sum[s]+=a[i];
	}for(int i=s+1;i<t;i++){
		if(b[i]!=1){
			sum[i]=0,b[i]=0;
			for(int j=siz*(i-1)+1;j<=siz*i;j++){//这里没办法,只能暴力
				a[j]=(long long)sqrt(a[j]);
			}for(int j=siz*(i-1)+1;j<=siz*i;j++){
				sum[i]+=a[j];
				b[i]=max(b[i],a[j]);
			}
		}
	}sum[t]=0,b[t]=0;
	for(int i=r;id[i]==t;i--){
		a[i]=(long long)sqrt(a[i]);
	}for(int i=siz*(t-1)+1;i<=siz*t;i++){
		b[t]=max(b[t],a[i]);
		sum[t]+=a[i];
	}
}long long query(long long l,long long r){
	long long s=id[l],t=id[r],ret=0;
	if(s==t){
		for(int i=l;i<=r;i++){
			ret+=a[i];
		}return ret;
	}for(int i=l;id[i]==s;i++){
		ret+=a[i];
	}for(int i=s+1;i<t;i++){
		if(b[i]!=1){
			ret+=sum[i];
		}else{
			ret+=siz;//直接加长度
		}
	}for(int i=r;id[i]==t;i--){
		ret+=a[i];
	}return ret;
}int main(){
	scanf("%lld",&n);
	siz=sqrt(n);//算长度
	for(int i=1;i<=n;i++){//分块
		scanf("%lld",&a[i]);
		id[i]=(i-1)/siz+1;
		b[(i-1)/siz+1]=max(b[(i-1)/siz+1],a[i]);
		sum[(i-1)/siz+1]+=a[i];
	}scanf("%lld",&m);
	for(int i=1;i<=m;i++){
		scanf("%lld%lld%lld",&k,&l,&r);
		if(k==0){
			if(l>r){//l>r是存在的!!
				swap(l,r);
			}updata(l,r);
		}else{
			if(l>r){
				swap(l,r);
			}printf("%lld\n",query(l,r));
		}
	}return 0;
}

P3870 [TJOI2009] 开关

题目描述

现有 n 盏灯排成一排,从左到右依次编号为:12,……,n。然后依次执行 m 项操作。

操作分为两种:

  1. 指定一个区间 [a,b],然后改变编号在这个区间内的灯的状态(把开着的灯关上,关着的灯打开);
  2. 指定一个区间 [a,b],要求你输出这个区间内有多少盏灯是打开的。

灯在初始时都是关着的。

输入格式

第一行有两个整数 nm,分别表示灯的数目和操作的数目。

接下来有 m 行,每行有三个整数,依次为:cab。其中 c 表示操作的种类。

  • c 的值为 0 时,表示是第一种操作。
  • c 的值为 1 时,表示是第二种操作。

ab 则分别表示了操作区间的左右边界。

输出格式

每当遇到第二种操作时,输出一行,包含一个整数,表示此时在查询的区间中打开的灯的数目。

提示

对于全部的测试点,保证 2n1051m1051a,bnc{0,1}


在反转的时候,整块就直接记录每一块 0 的数量和 1 的数量,后面交换就可以了,但是 ai 没变,于是要将反转的次数标记,因为反转两次就是没反转,那标记时异或 1 就可以了,然后散块要先将标记给下传然后清空,再暴力。

在查询的时候,散块一定要先将标记下传、清空,才可以再暴力,整块直接加上前面记录的 1 的数量就行了。

CODE:

#include<bits/stdc++.h>
using namespace std;
int n,m,k,l,r,id[100010],siz,a[100010],cnt[100010],cnt0[100010],tmp[200010];
void updata(int l,int r){
	int s=id[l],t=id[r];
	if(s==t){
		cnt[s]=0,cnt0[s]=0;
		if(tmp[s]==1){
			tmp[s]=0;
			for(int i=siz*(s-1)+1;i<=min(n,siz*s);i++){//下传标记
				if(a[i]==0){
					a[i]=1;
				}else if(a[i]==1){
					a[i]=0;
				}	
			}	
		}for(int i=l;i<=r;i++){
			if(a[i]==0){
				a[i]=1;
			}else if(a[i]==1){
				a[i]=0;
			}
		}for(int i=siz*(s-1)+1;i<=min(n,siz*s);i++){
			if(a[i]==1){
				cnt[s]++;
			}else{
				cnt0[s]++;
			}
		}return;
	}cnt[s]=0,cnt0[s]=0;
	if(tmp[s]==1){
		tmp[s]=0;
		for(int i=siz*(s-1)+1;i<=min(n,siz*s);i++){
			if(a[i]==0){
				a[i]=1;
			}else if(a[i]==1){
				a[i]=0;
			}	
		}	
	}for(int i=l;id[i]==s;i++){
		if(a[i]==0){
			a[i]=1;
		}else if(a[i]==1){
			a[i]=0;
		}
	}for(int i=siz*(s-1)+1;i<=min(n,siz*s);i++){
		if(a[i]==1){
			cnt[s]++;
		}else{
			cnt0[s]++;
		}
	}for(int i=s+1;i<t;i++){
		swap(cnt[i],cnt0[i]);//交换和标记
		tmp[i]++;
		if(tmp[i]==2){
			tmp[i]=0;
		}
	}cnt[t]=0,cnt0[t]=0;
	if(tmp[t]==1){
		tmp[t]=0;
		for(int i=siz*(t-1)+1;i<=min(n,siz*t);i++){
			if(a[i]==0){
				a[i]=1;
			}else if(a[i]==1){
				a[i]=0;
			}	
		}	
	}for(int i=r;id[i]==t;i--){
		if(a[i]==0){
			a[i]=1;
		}else if(a[i]==1){
			a[i]=0;
		}
	}for(int i=siz*(t-1)+1;i<=min(n,siz*t);i++){
		if(a[i]==1){
			cnt[t]++;
		}else{
			cnt0[t]++;
		}
	}
}int query(int l,int r){
	int s=id[l],t=id[r],ret=0;
	if(s==t){
		if(tmp[s]==1){
			tmp[s]=0;
			for(int i=siz*(s-1)+1;i<=min(n,siz*s);i++){//下传标记
				if(a[i]==0){
					a[i]=1;
				}else if(a[i]==1){
					a[i]=0;
				}	
			}	
		}for(int i=l;i<=r;i++){
			if(a[i]==1){
				ret++;
			}
		}return ret;
	}if(tmp[s]==1){
		tmp[s]=0;
		for(int i=siz*(s-1)+1;i<=min(n,siz*s);i++){
			if(a[i]==0){
				a[i]=1;
			}else if(a[i]==1){
				a[i]=0;	
			}	
		}	
	}for(int i=l;id[i]==s;i++){
		if(a[i]==1){
			ret++;
		}
	}for(int i=s+1;i<t;i++){
		ret+=cnt[i];
	}if(tmp[t]==1){
		tmp[t]=0;
		for(int i=siz*(t-1)+1;i<=min(n,siz*t);i++){
			if(a[i]==0){
				a[i]=1;
			}else if(a[i]==1){
				a[i]=0;
			}	
		}
	}for(int i=r;id[i]==t;i--){
		if(a[i]==1){
			ret++;
		}
	}return ret;
}int main(){
	scanf("%d",&n);
	siz=sqrt(n);
	for(int i=1;i<=n;i++){
		id[i]=(i-1)/siz+1;
		cnt0[(i-1)/siz+1]++;//0的数量
	}scanf("%d",&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&k,&l,&r);
		if(k==0){
			if(l>r){
				swap(l,r);
			}updata(l,r);
		}else{
			if(l>r){
				swap(l,r);
			}printf("%d\n",query(l,r));
		}
	}return 0;
}

咕咕咕

posted @   scy_qwq  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示