C++线段树初步(下)
来源:互联网 发布:购物群软件 编辑:程序博客网 时间:2024/06/04 18:52
上期博客中我们谈到了基本线段树中的不足:区间整体操作效率低。那么,我们该如何优化线段树使其能够高效处理区间内的整体操作?
废话不说,先上张模拟图。
(博主手绘…将就着看一下吧…)
如上图,这是一棵完全的线段树,现在要使区间[2,5]的所有数据增加x。
在进行修改操作的时候需要遍历到的节点在图中已用蓝色箭头标出。不难发现,当我们遍历到[3,4]时整个区间已被包含在内,只需要将改变的值存入该节点中,不需要再向下遍历到子节点了。事实上,若每个叶节点都遍历并修改,其时间复杂度还不如在数组中直接修改。
具体实现方法:将线段树的结构体中增加一个变量seg,代表该父节点下所有子节点的变化量。当需要再次向下遍历、更新到子节点的时候将此遍历迁移到子节点上,同时使父节点上的此变量归零。
struct sd{ int left,right,seg,maxx,sum; sd () { memset(this,0,sizeof(this)); }
博主事先把每个点的初存入了pre数组。
void pushup(int t){ tree[t].sum=tree[t*2].sum+tree[t*2+1].sum; tree[t].maxx=max(tree[t*2].maxx,tree[t*2+1].maxx); return;}void build_tree(int t,int lef,int rig){ tree[t].left=lef;tree[t].right=rig; if(lef==rig) { tree[t].sum=pre[lef]; tree[t].maxx=pre[lef]; return; } int mid=(lef+rig)/2; build_tree(t*2,lef,mid); build_tree(t*2+1,mid+1,rig); pushup(t);}
构造和更新操作和上期提到的简单线段树差不多。
接下来是至关重要的设置标记的环节。
void pushdown(int t,int lef,int rig){ int mid=(lef+rig)/2; tree[t*2].seg+=tree[t].seg;//左子序列继承 tree[t*2+1].seg+=tree[t].seg;//右子序列继承 tree[t*2].maxx+=tree[t].seg;//更改最大值 tree[t*2+1].maxx+=tree[t].seg; tree[t*2].sum+=(mid-lef+1)*tree[t].seg;//更改和 tree[t*2+1].sum+=(rig-mid)*tree[t].seg; //(rig-(mid+1)+1)*tree[t].seg; tree[t].seg=0;//清空父节点标记}
这个步骤中值得注意的是左子序列和右子序列的边间不要混淆,以build_tree中的对应关系为准。
然后是修改数据的操作:
void updata_add(int t,int lef,int rig,int lefbound,int rigbound,int val)//初次看时可能会有些晕,分别代表当前tree下标,左边界,右边界//查询的左右边界,更新的值{ if(lef>=lefbound&&rig<=rigbound)//若完全包含,则不再向下更新 { tree[t].seg+=val; tree[t].maxx+=val; tree[t].sum+=(rig-lef+1)*val;//这个+1不能少 return; } if(tree[t].seg!=0)//向下扩展一层 { pushdown(t,lef,rig); } int mid=(lef+rig)/2; if(lefbound<=mid)//左右递归搜索更改 { updata_add(t*2,lef,mid,lefbound,rigbound,val); } if(rigbound>mid) { updata_add(t*2+1,mid+1,rig,lefbound,rigbound,val); } pushup(t);//更新父节点}
接下来是两个查询函数,大同小异,放在一起讲解
int inquiry_sum(int t,int lef,int rig,int lefbound,int rigbound)//意义同上{ if(lefbound<=lef&&rigbound>=rig) { return tree[t].sum;//完全包含在区间查询范围中直接返回值。 } if(tree[t].seg!=0)//向下扩展一层 { pushdown(t,lef,rig); } int mid=(lef+rig)/2,sumnow=0; if(lefbound<=mid) sumnow+=inquiry_sum(t*2,lef,mid,lefbound,rigbound); if(rigbound>=mid+1) sumnow+=inquiry_sum(t*2+1,mid+1,rig,lefbound,rigbound); return sumnow;} int inquiry_max(int t,int lef,int rig,int lefbound,int rigbound){ if(lefbound<=lef&&rigbound>=rig)//搜索左右子区间 { return tree[t].maxx; } if(tree[t].seg!=0) { pushdown(t,lef,rig); } int mid=(lef+rig)/2,maxxx=0; if(lefbound<=mid) { maxxx=max(maxxx,inquiry_max(t*2,lef,mid,lefbound,rigbound)); } if(rigbound>=mid+1) { maxxx=max(maxxx,inquiry_max(t*2+1,mid+1,rig,lefbound,rigbound)); } return maxxx;}
总结:线段树区间操作优化其实就是延迟更新节点至必要时,来节约更改数值时每次不必要的更新子节点所花费的时间。但若要完全将一个区间内元素修改成某特定值,博主个人觉得还需一个seg2记录,更新时另写一个函数同样利用pushdown和pushup来操作,请读者自行思考解决。
最后,贴上博主的辣鸡代码(写到了3000+)
#include<bits/stdc++.h>using namespace std;struct sd{ int left,right,seg,maxx,sum; sd () { memset(this,0,sizeof(this)); }};sd tree[500000];int pre[100000];void pushup(int t){ tree[t].sum=tree[t*2].sum+tree[t*2+1].sum; tree[t].maxx=max(tree[t*2].maxx,tree[t*2+1].maxx); return;}void build_tree(int t,int lef,int rig){ tree[t].left=lef;tree[t].right=rig; if(lef==rig) { tree[t].sum=pre[lef]; tree[t].maxx=pre[lef]; return; } int mid=(lef+rig)/2; build_tree(t*2,lef,mid); build_tree(t*2+1,mid+1,rig); pushup(t);}void pushdown(int t,int lef,int rig){ int mid=(lef+rig)/2; tree[t*2].seg+=tree[t].seg; tree[t*2+1].seg+=tree[t].seg; tree[t*2].maxx+=tree[t].seg; tree[t*2+1].maxx+=tree[t].seg; tree[t*2].sum+=(mid-lef+1)*tree[t].seg; tree[t*2+1].sum+=(rig-mid)*tree[t].seg; tree[t].seg=0;}void updata_add(int t,int lef,int rig,int lefbound,int rigbound,int val){ if(lef>=lefbound&&rig<=rigbound) { tree[t].seg+=val; tree[t].maxx+=val; tree[t].sum+=(rig-lef+1)*val; return; } if(tree[t].seg!=0) { pushdown(t,lef,rig); } int mid=(lef+rig)/2; if(lefbound<=mid) { updata_add(t*2,lef,mid,lefbound,rigbound,val); } if(rigbound>mid) { updata_add(t*2+1,mid+1,rig,lefbound,rigbound,val); } pushup(t);}int inquiry_sum(int t,int lef,int rig,int lefbound,int rigbound){ if(lefbound<=lef&&rigbound>=rig) { return tree[t].sum; } if(tree[t].seg!=0) { pushdown(t,lef,rig); } int mid=(lef+rig)/2,sumnow=0; if(lefbound<=mid) sumnow+=inquiry_sum(t*2,lef,mid,lefbound,rigbound); if(rigbound>=mid+1) sumnow+=inquiry_sum(t*2+1,mid+1,rig,lefbound,rigbound); return sumnow;} int inquiry_max(int t,int lef,int rig,int lefbound,int rigbound){ if(lefbound<=lef&&rigbound>=rig) { return tree[t].maxx; } if(tree[t].seg!=0) { pushdown(t,lef,rig); } int mid=(lef+rig)/2,maxxx=0; if(lefbound<=mid) { maxxx=max(maxxx,inquiry_max(t*2,lef,mid,lefbound,rigbound)); } if(rigbound>=mid+1) { maxxx=max(maxxx,inquiry_max(t*2+1,mid+1,rig,lefbound,rigbound)); } return maxxx;}int main(){ int num,a,b,c,d; printf("请输入数据个数(不超过1e6):\n"); scanf("%d",&num); printf("请输入数据:"); for(int i=1;i<=num;i++) { scanf("%d",&pre[i]); } build_tree(1,1,num); printf("\n"); printf("输入成功!开始进行操作\n操作指导:\n1,x,y-->查询x-y的和\n2,x,y-->查询x-y的最大值\n3,x,y,k-->将x-y数据加上k\n若要退出程序,请输入0\n"); while (true) { int k=1; while(tree[k].sum !=0) { printf("%d ",tree[k].sum); k++; } printf("\n"); scanf("%d",&a); if(a==0)break; if(a==1) { scanf("%d%d",&b,&c); printf("第%d至第%d个数据的和是:%d\n",b,c,inquiry_sum(1,1,num,b,c)); continue; } if(a==2) { scanf("%d%d",&b,&c); printf("第%d至第%d个数据的最大值是:%d\n",b,c,inquiry_max(1,1,num,b,c)); continue; } if(a==3) { scanf("%d%d%d",&b,&c,&d); updata_add(1,1,num,b,c,d); printf("操作成功!\n"); continue; } } printf("感谢使用!"); return 0;}
以后的各档博客,我们将会继续讨论有关树的算法与数据结构,我们下一次不见不散,欢迎广大读者留言讨论。
- C++线段树初步(下)
- C++线段树初步(上)
- 线段树初步理解...
- 初步线段树 hdu1166
- 线段树初步
- 线段树初步
- 线段树初步__ZERO__.
- 线段树_初步
- hdu 1166 线段树 (初步)
- 线段树 初步:hdu 1166
- HDU 1754 I Hate It(线段树初步应用)
- C语言初步知识与基本数据类型(下)
- hdu 1166 敌兵布阵 (线段树初步)
- HDU 1166 敌兵布阵(线段树的初步应用2)
- uva 12299 RMQ with Shifts(线段树单点更新初步应用)
- 树套树:二维线段树初步:hdu1823——Luck and Love(单点修改,区间查询)
- poj 3468 的一些见解(线段树的初步学习)
- Codeforces 444C(线段树)
- mac上安装xgboost报错
- mysql优化
- Codeforces 873 D Merge Sort 【分治】
- 手把手教你用webpack来搭建一个项目
- Linux CentOS 7 安装字体库 & 中文字体
- C++线段树初步(下)
- Redis 在Centos7下配置开机自启动
- 补齐函数求数根
- SVN问题-Failed to load JavaHL Library
- PAT (Basic Level) Practise (中文)1003. 我要通过!(20)
- 上传下载
- while,do while,for循环总结
- CSS盒子模型
- angular1项目中bower的使用