【学习笔记】分块学习笔记
分块经常听别人提起,我也学一下。
正片
分块就是将一个数列分成很多块,然后每块单独操作,最后的结果放到原数列里。
分块的题目类型经常是区间中修改和查询。
这里,一个长度为
先来看例题吧。
例题
P2357 守墓人
题目背景
在一个荒凉的墓地上,有一个令人尊敬的守墓人, 他看守的墓地从来没有被盗过, 所以人们很放心的把自己的先人的墓安顿在他那
守墓人能看好这片墓地是必然而不是偶然……
因为……守墓人懂风水 0.0
题目描述
他把墓地分为主要墓碑和次要墓碑, 主要墓碑只能有
而每个墓碑有一个初始的风水值,这些风水值决定了墓地的风水的好坏,所以守墓人需要经常来查询这些墓碑。
善于运用风水的守墓人,通过一次次逆天改命,使得自己拥有了无限寿命,没人知道他活了多久。这天,你幸运的拜访到了他,他要求你和他共同见证接下来几年他的战果,但不过他每次统计风水值之和都需要你来帮他计算,算错了他会要你命 QAQ
风水也不是不可变,除非遭遇特殊情况,已知在接下来的
- 将
这个区间所有的墓碑的风水值增加 。
2.将主墓碑的风水值增加
3.将主墓碑的风水值减少
4.统计
5.求主墓碑的风水值
上面也说了,很多人会把先人的墓安居在这里,而且守墓人活了很多世纪→_→,墓碑的数量会多的你不敢相信= =
守墓人和善的邀请你帮他完成这些操作,要不然哪天你的旅馆爆炸了,天上下刀子.....
为了活命,还是帮他吧
输入格式
第一行,两个正整数
第二行,
接下来
输出格式
输出会有若干行,对
提示
这道题可以用线段树,但是我用分块做。
主墓碑的修改和查询很简单,我们先看区间的修改和查询。
其实在修改的时候,我们可以发现:
1.这个区间中会有一些完整的块,我们就可以不用修改
2.区间中的两头也会有不完整的块,我们直接遍历这个块,暴力修改就行了,但是要记得改掉记录的这个块中
再来看看查询,仍然是有两种块:
1.如果是完整块,直接将存总和的变量加上这个块中
2.如果是不完整块,需要加上目前的
还是挺简单的吧!
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 难度,于是便有了数据范围。
第五分钟,诗人说,要有韵律,于是便有了时间限制和内存限制。
第六分钟,和雪说,要省点事,于是便有了保证运算过程中及最终结果均不超过
第七分钟,这道题终于造完了,然而,造题的神牛们再也不想写这道题的程序了。"
——《上帝造题的七分钟·第二部》
所以这个神圣的任务就交给你了。
输入格式
第一行一个整数
第二行
第三行一个整数
接下来 k l r
。
-
表示给 中的每个数开平方(下取整)。 -
表示询问 中各个数的和。
数据中有可能
输出格式
对于询问操作,每行输出一个回答。
提示
对于
对于
这题也是分块,但是要有一个小优化。
我们可以发现,
那么如何判断是否全是
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] 开关
题目描述
现有
操作分为两种:
- 指定一个区间
,然后改变编号在这个区间内的灯的状态(把开着的灯关上,关着的灯打开); - 指定一个区间
,要求你输出这个区间内有多少盏灯是打开的。
灯在初始时都是关着的。
输入格式
第一行有两个整数
接下来有
- 当
的值为 时,表示是第一种操作。 - 当
的值为 时,表示是第二种操作。
输出格式
每当遇到第二种操作时,输出一行,包含一个整数,表示此时在查询的区间中打开的灯的数目。
提示
对于全部的测试点,保证
在反转的时候,整块就直接记录每一块
在查询的时候,散块一定要先将标记下传、清空,才可以再暴力,整块直接加上前面记录的
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;
}
咕咕咕
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探