线段树学习笔记及模板
来源:互联网 发布:魔卡幻想 淘宝 编辑:程序博客网 时间:2024/05/29 17:44
概括:线段树是一种数据结构,针对需要动态 修改并且获取数据中一段信息(子段和,子段最小值等)的数据来使用
大体思路:使用一颗完全二叉树(数组实现)记录数据。树的叶子节点记录每一个数据元素,非叶子节点记录段信息。
建树,查询,修改都由二叉树的性质:第i个节点的左右孩子为2*i+1,2*i+2 递归的执行
建树思路:从根向下递归找叶子->找到叶子后对叶子赋值->赋值完叶子后回溯赋段信息
查找(查询某一段的信息)思路:从根向下递归点的段信息:
对于和查找的段没有交集的段,剪枝掉
对于被要查找的段包住的段,符合要求,回溯
其他的段向下递归,回溯合并的段信息
点修改思路:从根向下递归找叶子,找到需要的点就修改,在回溯中更新段信息
特别的操作1:段修改
对一段元素做同一操作,势必会影响到非叶子节点。
对于像给一段元素加上同一值这样的操作,可以直接给代表这段的节点操作:记录到延时标志上面,并不更新其他的值,把更新操作留到查询操作中解决。
对于段赋值,就需要在对这一段修改前确保这一段处于改了也没问题的状态,即延时标记为0
由于下推操作只有O(1)的复杂度所以可以到处用
所以在进行修改前下推一下就行了
#include <bits/stdc++.h> using namespace std; const int maxn=1000; //用于存放线段树的结构 l,r表示此区间的左右端点 struct segn { int data,l,r,mi,su,mark_add; }segt[maxn]; //建树:叶节点终止条件->递归建左右子树->由左右孩子信息得到此节点信息 void push_dowm(int root) { if(segt[root].mark_add!=0) { int st=segt[root].l; int en=segt[root].r; segt[root].su+=segt[root].mark_add*(en-st+1); segt[root].mi+=segt[root].mark_add; segt[root*2+1].mark_add+=segt[root].mark_add; segt[root*2+2].mark_add+=segt[root].mark_add; segt[root].mark_add=0; } } void build(int *dat,int root,int st,int en) { //st,en表示目前区间的首尾端点序号,dat[]表示用于初始化的数据 //记录左右端点 segt[root].l=st; segt[root].r=en; if(st==en)//在叶子节点上加入元素数据 { segt[root].data=dat[st]; segt[root].mark_add=0; segt[root].mi=segt[root].data;//求区间最小值的操作 segt[root].su=segt[root].data;//求区间和的操作 return; } //对于非叶子节点 int mid=(st+en)/2;//分割点 //由二叉性,数组存树的左右节点编号分别为此节点的*2+1和*2+2 build(dat,root*2+1,st,mid);//递归建左子树 build(dat,root*2+2,mid+1,en);//右子树 //由左右子树得到此节点的数据 //求区间最小值 segt[root].mi=min(segt[root*2+1].mi,segt[root*2+2].mi); //求区间和 segt[root].su=segt[root*2+1].su+segt[root*2+2].su; } /* //查询的模板 int query_(int qst,int qen,int tst,int ten,int root) {//查询子段和 push_dowm(root); if(qst>ten||qen<tst)//想查的区间和正在查区间的没有重叠,剪枝 return 一个对合并值没有效果的值; if(tst>=qst&&ten<=qen)//正在查的区间比想查的小或者相等,符合 return segt[root].数据; int mid =(tst+ten)/2; return 子段合并操作//搜索左右子树 } */ const int bigmun=1e7; //下推操作在查询中完成 int query_mi(int qst,int qen,int tst,int ten,int root)//查询操作:向下递归的找区间,只要找到被要找的区间包括的区间,就可以肯定这个区间是需要的(因为向下递归的顺序和不交叉的顺序) {//查询最小值 //qst,qen:想查询的区间 tst,ten:正在查的区间 push_dowm(root); if(qst>ten||qen<tst)//想查的区间和正在查区间的没有重叠,剪枝 return bigmun; if(tst>=qst&&ten<=qen)//正在查的区间比想查的小或者相等,符合 return segt[root].mi; int mid =(tst+ten)/2; return min(query_mi(qst,qen,tst,mid,root*2+1),query_mi(qst,qen,mid+1,ten,root*2+2));//搜索左右子树 } int query_sum(int qst,int qen,int tst,int ten,int root) {//查询子段和 push_dowm(root); if(qst>ten||qen<tst)//想查的区间和正在查区间的没有重叠,剪枝 return 0; if(tst>=qst&&ten<=qen)//正在查的区间比想查的小或者相等,符合 return segt[root].su; int mid =(tst+ten)/2; return query_sum(qst,qen,tst,mid,root*2+1)+query_sum(qst,qen,mid+1,ten,root*2+2);//搜索左右子树 } void adjust_elemt(const int addval,const int pos,int st,int en,int root)//点修改,给pos位置的元素加上addval {//思路:递归一遍,查找并修改需要修改的元素,然后把路径更新一遍 if(st==en) { if(st==pos) {//在这里进行元素修改 segt[root].data+=addval; segt[root].mi+=addval;//最小值 segt[root].su+=addval;//子段和 } return; } int mid=(st+en)/2; if(pos<=mid) adjust_elemt(addval,pos,st,mid,root*2+1); else adjust_elemt(addval,pos,mid+1,en,root*2+2); //在这里进行段修改 segt[root].mi=min(segt[root*2+1].mi,segt[root*2+2].mi);//最小值段修改 segt[root].su=segt[root*2+1].su+segt[root*2+2].su;//子段和段修改 } void segadd(int qst,int qen,int tst,int ten,int root,int addnum) {//全段加 if(qst>ten||qen<tst)//不符合的区间 { return ; } if(tst>=qst&&ten<=qen)//找到要加的区间 { segt[root].mark_add+=addnum; return ; } int mid =(tst+ten)/2; if(tst>qst&&ten>qen)segt[root].su+=addnum*(qen-tst+1)*addnum; else if(tst<qst&&ten<qen)segt[root].su+=addnum*(ten-qst+1)*addnum; else segt[root].su+=addnum*(qen-qst+1); segadd(qst,qen,tst,mid,root*2+1,addnum); segadd(qst,qen,mid+1,ten,root*2+2,addnum);//搜索左右子树 // segt[root].mark_add+=addthis; // return addthis; } int main() { const int sz=17; int arr[sz+1]; for(int i=0;i<sz+1;i++)arr[i]=1; build(arr,0,0,sz); int j=2,cur; // adjust_elemt(-1000,4,0,50,0); segadd(0,3,0,sz,0,-1); for(int i=0;i<=sz;i++)query_mi(i,i,0,sz,0); for(int i=0;i<sz*2+1;i++) { cout<<segt[i].l<<"~"<<segt[i].r<</*":segmin:"<<segt[i].mi<<*/" segsum:"<<segt[i].su<</*" segmark:"<<segt[i].mark_add<<*/" "; if(i+2==j) { cout<<endl; j*=2; } } //测试build //cout<<query_mi(5,10,0,50,0); //cout<<query_sum(0,2,0,50,0); return 0; }
hdu 1166 敌兵布阵
直接套模板的题,然而神志不清的手滑了半天
#include<cstdio>#include<iostream> using namespace std; const int maxn=200000; int sz=1; //用于存放线段树的结构 l,r表示此区间的左右端点 struct segn { int data,l,r,mi,su,mark_add; }segt[maxn]; //建树:叶节点终止条件->递归建左右子树->由左右孩子信息得到此节点信息 void build(int *dat,int root,int st,int en) { //st,en表示目前区间的首尾端点序号,dat[]表示用于初始化的数据 //记录左右端点 if(st==en)//在叶子节点上加入元素数据 { segt[root].su=dat[st];//求区间和的操作 return; } //对于非叶子节点 int mid=(st+en)/2;//分割点 //由二叉性,数组存树的左右节点编号分别为此节点的*2+1和*2+2 build(dat,root*2+1,st,mid);//递归建左子树 build(dat,root*2+2,mid+1,en);//右子树 //由左右子树得到此节点的数据 //求区间最小值 //求区间和 segt[root].su=segt[root*2+1].su+segt[root*2+2].su; } int query_sum(int qst,int qen,int tst,int ten,int root) {//查询子段和 if(qst>ten||qen<tst)//想查的区间和正在查区间的没有重叠,剪枝 return 0; if(tst>=qst&&ten<=qen)//正在查的区间比想查的小或者相等,符合 return segt[root].su; int mid =(tst+ten)/2; return query_sum(qst,qen,tst,mid,root*2+1)+query_sum(qst,qen,mid+1,ten,root*2+2);//搜索左右子树 } void adjust_elemt(const int addval,const int pos,int st,int en,int root)//点修改,给pos位置的元素加上addval {//思路:递归一遍,查找并修改需要修改的元素,然后把路径更新一遍 /* if(en>sz) { int k=0; while(++k){k=1;} }*/ if(st==en) { if(st==pos) {//在这里进行元素修改 segt[root].su+=addval;//子段和 } return; } int mid=(st+en)/2; if(pos<=mid) adjust_elemt(addval,pos,st,mid,root*2+1); else adjust_elemt(addval,pos,mid+1,en,root*2+2); //在这里进行段修改 segt[root].su=segt[root*2+1].su+segt[root*2+2].su;//子段和段修改 } int main() { int t,n,arr[65536],counn=0; string comm; scanf("%d",&t); while(t--) { bool flag=true; scanf("%d",&n); for(int i=0;i<n;i++) { scanf("%d",&arr[i]); } sz=n; build(arr,0,0,sz-1); //for(int i=0;i<2*sz;i++)cout<<segt[i].su<<" "; int st,en,ad; cout<<"Case "<<++counn<<":"<<endl; while(cin>>comm) { if(comm=="End")break; if(comm=="Query") { scanf("%d%d",&st,&en); cout<<query_sum(st-1,en-1,0,sz-1,0)<<endl; continue; } if(comm=="Add"){scanf("%d%d",&st,&ad);} if(comm=="Sub"){scanf("%d%d",&st,&ad);ad=-ad;} adjust_elemt(ad,st-1,0,sz-1,0); } } return 0; }
hdu 1754
I Hate It
同样是模板题
#include <cstdio> #include<iostream> using namespace std; const int maxn=200000*4; int Max(int x,int y){return x>=y ? x:y;} //用于存放线段树的结构 l,r表示此区间的左右端点 struct segn { int data,l,r,mi,su,mark_add; }segt[maxn]; //建树:叶节点终止条件->递归建左右子树->由左右孩子信息得到此节点信息 void build(int *dat,int root,int st,int en) { //st,en表示目前区间的首尾端点序号,dat[]表示用于初始化的数据 //记录左右端点 if(st==en)//在叶子节点上加入元素数据 { segt[root].mi=dat[st];//求区间最小值的操作 return; } //对于非叶子节点 int mid=(st+en)/2;//分割点 //由二叉性,数组存树的左右节点编号分别为此节点的*2+1和*2+2 build(dat,root*2+1,st,mid);//递归建左子树 build(dat,root*2+2,mid+1,en);//右子树 //由左右子树得到此节点的数据 //求区间最小值 segt[root].mi=Max(segt[root*2+1].mi,segt[root*2+2].mi); //求区间和 segt[root].su=segt[root*2+1].su+segt[root*2+2].su; } int query_mi(int qst,int qen,int tst,int ten,int root)//查询操作:向下递归的找区间,只要找到被要找的区间包括的区间,就可以肯定这个区间是需要的(因为向下递归的顺序和不交叉的顺序) {//查询最小值 //qst,qen:想查询的区间 tst,ten:正在查的区间 if(qst>ten||qen<tst)//想查的区间和正在查区间的没有重叠,剪枝 return 0; if(tst>=qst&&ten<=qen)//正在查的区间比想查的小或者相等,符合 return segt[root].mi; int mid =(tst+ten)/2; return Max(query_mi(qst,qen,tst,mid,root*2+1),query_mi(qst,qen,mid+1,ten,root*2+2));//搜索左右子树 } void adjust_elemt(const int addval,const int pos,int st,int en,int root)//点修改,给pos位置的元素加上addval {//思路:递归一遍,查找并修改需要修改的元素,然后把路径更新一遍 if(st==en) { if(st==pos) {//在这里进行元素修改 segt[root].mi=addval;//最大值 } return; } int mid=(st+en)/2; if(pos<=mid) adjust_elemt(addval,pos,st,mid,root*2+1); else adjust_elemt(addval,pos,mid+1,en,root*2+2); //在这里进行段修改 segt[root].mi=Max(segt[root*2+1].mi,segt[root*2+2].mi);//最大值段修改 } int main() { int xss,czs; while(~scanf("%d%d",&xss,&czs)) { int arr[200005],cur; for(int i=0;i<xss;i++) { scanf("%d",&cur); arr[i]=cur; } build(arr,0,0,xss-1);//for(int i=0;i<2*xss;i++)cout<<segt[i].mi<<" "; while(czs--) { char c[4]; int st,en; scanf("%s",c);//cout<<"_____"<<c<<"______"; if(c[0]=='Q') { scanf("%d%d",&st,&en); printf("%d\n",query_mi(st-1,en-1,0,xss-1,0)); } else { scanf("%d%d",&st,&en); adjust_elemt(en,st-1,0,xss-1,0); // for(int i=0;i<2*xss;i++)cout<<endl<<segt[i].mi<<" "; } } } return 0; }
2017.11.14
做题的时候发现模板前面的代码写的太烂,一方面是多余的东西太多,一方面是段修改写错了
拿hdu1698的代码改改补个有段操作的新模板(点查询还没写)(段最值操作也没写)
#include <cstdio>#include<iostream> using namespace std; const int maxn=500100; struct segn { int su,mark_add; }segt[maxn]; //下推的root是正在改的点的位置,st,en是这个点的区间 void push_dowm_segadj(int root,int st,int en)//段加的下推 { if(segt[root].mark_add!=0) { segt[root*2+1].su=((st+en)/2-st+1)*segt[root].mark_add; segt[root*2+2].su=(en-(st+en)/2)*segt[root].mark_add; segt[root*2+1].mark_add=segt[root].mark_add; segt[root*2+2].mark_add=segt[root].mark_add; segt[root].mark_add=0; } } void push_dowm_segadd(int root,int st,int en)//段赋值的下推 { if(segt[root].mark_add!=0) { segt[root*2+1].su+=((st+en)/2-st+1)*segt[root].mark_add; segt[root*2+2].su+=(en-(st+en)/2)*segt[root].mark_add; segt[root*2+1].mark_add+=segt[root].mark_add; segt[root*2+2].mark_add+=segt[root].mark_add; segt[root].mark_add=0; } } void build(int *arr,int root,int st,int en)//arr:赋值数组 root,st写0 en写arr的长度-1 { if(st==en) { segt[root].mark_add=0; segt[root].su=arr[st]; return; } int mid=(st+en)/2; build(arr,root*2+1,st,mid); build(arr,root*2+2,mid+1,en); segt[root].su=segt[root*2+1].su+segt[root*2+2].su; } void segadj(int qst,int qen,int tst,int ten,int root,int addnum) {//段赋值 q:查询区间 t:正在搞的区间,写0和n-1 addnum;改成的值 if(qst>ten||qen<tst)//不符合的区间 { return ; } if(tst>=qst&&ten<=qen)//找到要改的区间 { segt[root].mark_add=addnum; segt[root].su=addnum*(ten-tst+1); return ; } int mid =(tst+ten)/2; push_dowm_segadj(root,tst,ten); segadj(qst,qen,tst,mid,root*2+1,addnum); segadj(qst,qen,mid+1,ten,root*2+2,addnum);//搜索左右子树 } void segadj(int qst,int qen,int tst,int ten,int root,int addnum) {//段修改 q:查询区间 t:正在搞的区间,写0和n-1 addnum;改成的值 if(qst>ten||qen<tst)//不符合的区间 { return ; } if(tst>=qst&&ten<=qen)//找到要加的区间 { segt[root].mark_add=addnum; segt[root].su=addnum*(ten-tst+1); return ; } int mid =(tst+ten)/2; push_dowm_segadd(root,tst,ten); segadd(qst,qen,tst,mid,root*2+1,addnum); segadd(qst,qen,mid+1,ten,root*2+2,addnum);//搜索左右子树 } /* void upd(int root,int st,int en)//对于全部改完后才进行查询的题,可以最后进行整体下推 { if(st==en)return; push_dowm(root,st,en);//按需求改这个下推 upd(2*root+1,st,(st+en)/2); upd(2*root+2,(st+en)/2+1,en); segt[root].su=segt[root*2+1].su+segt[root*2+2].su; } */
- 线段树学习笔记及模板
- 线段树 模板 及 解释
- 线段树小解及模板
- 线段树模板及习题
- 线段树(学习模板)
- 【线段树】Codevs线段树练习1.2.3及线段树学习笔记
- splay学习笔记及模板
- 学习笔记 线段树
- 线段树学习笔记
- 线段树学习笔记
- 学习笔记 --- 线段树
- 线段树学习笔记
- 线段树学习笔记
- 线段树学习笔记
- 线段树模板及技巧分析
- zkw线段树模板及理解
- 线段树学习笔记01
- Algorithm学习笔记 --- 线段树
- 自学自动化测试Python2.7.14+selenium 2.53.6 +pycharm
- Oc AlertView 添加/删除/修改 到表格 -demo
- ES6-正则的扩展-sticky属性和flags属性
- Oc Snow Animation(下雪动画)~demo
- 解决 Cannot open D:\Program Files\Anaconda3\Scripts\pip-script.py 问题
- 线段树学习笔记及模板
- 【机房收费】这一路走来的弯路
- WebCrawler-HttpClient
- servlet简介及ServletContext对象
- 螺旋矩阵
- 【贪心】最优合并问题
- WebService学习(二)——WebService相关概念介绍
- 20171031js 中 string 转function
- ASP.NET