线段树 学习笔记
板子题
1474: 【区间维护】小A的课堂2
时间限制: 1 Sec 内存限制: 128 MB
题目描述
小A同学总是在FLY的课上时处于神游状态亦或是休眠状态,所以小A对FLY到底讲了什么是一无所知。然而,FLY总是打断小A的休眠状态,并问他问题。作为小A的小伙伴,你当然不希望小A同学翻车(不然下一个回答问题的人就是你啦)。所以你需要编写个程序帮助小A求出每个知识点到底讲的难度有多深。已知FLY在课上一共扯到了1到N个知识点,每个知识点都有一个初始难度值,FLY会进行M个动作(每个动作包括讲课和提问),其中,
动作1:讲课,该动作会使得一段连续的知识点的难度值上升P(P有可能为负数)
动作2:提问,该动作需要小A回答某一段连续知识点的难度值之和
输入
第一行包括两个数字,,表示知识点个数和FLY的动作次数
第二行包括个数字,分别表示到个知识点的难度值
接下来行,每行代表操作,若第个数字为,则代表该动作为讲课,接下来是三个变量,代表到区间的知识点难度值上升;若第个数字为,则代表该动作为提问,接下来是个变量,代表询问之间的难度值和。
输出
每次小A的回答,即每次询问的难度值,每次回答用换行隔开
样例输入
5 4 3 2 4 -3 1 2 3 4 1 1 4 1 1 2 3 -3 2 3 4
样例输出
1 0
提示
对于50%的数据,;
对于100%的数据,;
每个知识点难度值;
附洛谷原题:
1.线段树 模板1
2.线段树 模板2 进击版
3.庙会班车 聪明题
算法实现&代码实现
采用二分的方法来进行区间信息维护。形成一棵树。
修改复杂度:
查找复杂度:
建树
首先,要建树才能对线段树进行操作。
利用二分-递归-回溯的思想。
递归时,需要三个量:
左端点,右端点,节点编号(像堆一样传递)。
也就是说,若父亲节点为,
左儿子就为
右儿子就为
卡常写法:n<<1 n<<1|1
scanf("%d%d",&n,&T); for(int i=1;i<=n;i++) scanf("%d",&a[i]); build(1,n,1);
就这样
void up(int rt){ sum[rt]=sum[rt<<1]+sum[rt<<1|1]; } void build(int l,int r,int rt){//up是累加函数 if(l==r){ sum[rt]=a[l]; return; } int m=(l+r)>>1;//卡常 build(l,m,rt<<1); build(m+1,r,rt<<1|1); up(rt); return; }
查找
然后是查找,与建树一样,采用二分-递归-回溯的写法
int find(int l,int r,int rt){ if(head<=l&&tail>=r)//head,tail是范围 return sum[rt];//两个变量在主函数内输入 int m=(l+r)>>1; int s=0; if(head<=m) s+=find(l,m,rt<<1);//递归查找左子树 if(tail>m) s+=find(m+1,r,rt<<1|1);//递归查找右子树 return s; }
重头戏----修改
像分块一样,我们用一个懒惰标记 数组来保存修改的量。
还是二分修改
void update(int l,int r,int rt){ if(head<=l&&tail>=r){ f[rt]+=v;//head,tail是范围 v是增值 sum[rt]+=v*(r-l+1); //三个变量在主函数内输入 return; } int m=(l+r)>>1; if(head<=m) update(l,m,rt<<1);//左右查找 if(tail>m) update(m+1,r,rt<<1|1); up(rt);//累加 return; }
但是
注意,如果 被修改了,那么, 也应该修改,所以,我们需要下推标记。
void down(int rt,int l,int r){ if(f[rt]){//l 是左子树的长度,r 是右子树的长度 f[rt<<1]+=f[rt];//要用+=,因为f[rt<<1]可能不等于0 f[rt<<1|1]+=f[rt];//同上 sum[rt<<1]+=f[rt]*l;//sum累加 sum[rt<<1|1]+=f[rt]*r;//sum累加 f[rt]=0;//标记已经下推,标记清零 } return; }
但是,在搜索的时候,也要下推标记,不然。。。
所以正确的修改和搜索是这样的:
int find(int l,int r,int rt){ if(head<=l&&tail>=r) return sum[rt]; int m=(l+r)>>1; down(rt,m-l+1,r-m);//递归搜索前下推标记 int s=0; if(head<=m) s+=find(l,m,rt<<1); if(tail>m) s+=find(m+1,r,rt<<1|1); return s; } void update(int l,int r,int rt){ if(head<=l&&tail>=r){ f[rt]+=v; sum[rt]+=v*(r-l+1); return; } int m=(l+r)>>1; down(rt,m-l+1,r-m);//查找前下推标记 if(head<=m) update(l,m,rt<<1); if(tail>m) update(m+1,r,rt<<1|1); up(rt); return; }
模板
这个模板,除掉主函数什么的,有行。
最后希望大家使用自己的码风,创建一份自己的代码,背下来,尽量在10分钟之内打出来(因为考的一般不是模板)。
#include<cstdio> #define maxn 200001 using namespace std; int sum[maxn<<2],a[maxn],n,T,f[maxn<<2]; int head,tail,v,k; void up(int rt){ sum[rt]=sum[rt<<1]+sum[rt<<1|1]; } void build(int,int,int); int find(int,int,int); void down(int,int,int); void update(int,int,int); int main(){ scanf("%d%d",&n,&T); for(int i=1;i<=n;i++) scanf("%d",&a[i]); build(1,n,1); while(T--){ scanf("%d",&k); if(k==1){//修改 scanf("%d%d%d",&head,&tail,&v); update(1,n,1); } else{//查询 scanf("%d%d",&head,&tail); printf("%d\n",find(1,n,1)); } } return 0; } void build(int l,int r,int rt){ if(l==r){ sum[rt]=a[l]; return; } int m=(l+r)>>1; build(l,m,rt<<1); build(m+1,r,rt<<1|1); up(rt); return; } int find(int l,int r,int rt){ if(head<=l&&tail>=r) return sum[rt]; int m=(l+r)>>1; down(rt,m-l+1,r-m); int s=0; if(head<=m) s+=find(l,m,rt<<1); if(tail>m) s+=find(m+1,r,rt<<1|1); return s; } void down(int rt,int l,int r){ if(f[rt]){ f[rt<<1]+=f[rt]; f[rt<<1|1]+=f[rt]; sum[rt<<1]+=f[rt]*l; sum[rt<<1|1]+=f[rt]*r; f[rt]=0; } return; } void update(int l,int r,int rt){ if(head<=l&&tail>=r){ f[rt]+=v; sum[rt]+=v*(r-l+1); return; } int m=(l+r)>>1; down(rt,m-l+1,r-m); if(head<=m) update(l,m,rt<<1); if(tail>m) update(m+1,r,rt<<1|1); up(rt); return; }
原题解析
但是,如果用这个代码交上去的话。。。
因为这题数据会炸int,所以,我们需要把一些地方改成,还要加几个,尤其是乘法,不然会按照int的方式来计算。。。依然如旧
AC代码:耗尽个月才A的,
#include<cstdio> #define maxn 200001 using namespace std; long long sum[maxn<<2],f[maxn<<2]; int head,tail,v,k,a[maxn],n,T; void up(int rt){ sum[rt]=sum[rt<<1]+sum[rt<<1|1]; } void build(int,int,int); long long find(int,int,int); void down(int,int,int); void update(int,int,int); int main(){ scanf("%d%d",&n,&T); for(int i=1;i<=n;i++) scanf("%d",&a[i]); build(1,n,1); while(T--){ scanf("%d",&k); if(k==1){//修改 scanf("%d%d%d",&head,&tail,&v); update(1,n,1); } else{//查询 scanf("%d%d",&head,&tail); printf("%lld\n",find(1,n,1)); } } return 0; } void build(int l,int r,int rt){ if(l==r){ sum[rt]=a[l]; return; } int m=(l+r)>>1; build(l,m,rt<<1); build(m+1,r,rt<<1|1); up(rt); return; } long long find(int l,int r,int rt){ if(head<=l&&tail>=r) return sum[rt]; int m=(l+r)>>1; down(rt,m-l+1,r-m); long long s=0; if(head<=m) s+=find(l,m,rt<<1); if(tail>m) s+=find(m+1,r,rt<<1|1); return s; } void down(int rt,int l,int r){ if(f[rt]){ f[rt<<1]+=f[rt]; f[rt<<1|1]+=f[rt]; sum[rt<<1]+=f[rt]*(long long)l; sum[rt<<1|1]+=f[rt]*(long long)r; f[rt]=0; } return; } void update(int l,int r,int rt){ if(head<=l&&tail>=r){ f[rt]+=v; sum[rt]+=(long long)v*(long long)(r-l+1); return; } int m=(l+r)>>1; down(rt,m-l+1,r-m); if(head<=m) update(l,m,rt<<1); if(tail>m) update(m+1,r,rt<<1|1); up(rt); return; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具