总结之线段树1
2016-07-27 14:33
381 查看
线段树是利用了完全二叉树的一种数据结构,在每个节点保存一条线段,可以维护和询问一组数据,主要用于解决一段数据的动态查询问题。线段树需要维护的是子叶和根之间的变化关系并且可以查询,在预处理时耗时O(n),查询,更新操作需要o(logn),空间消耗o(n)。
线段树单点更新,每次修改对应子叶节点,相应的根节点也要改变,如hduoj 1754 I Hate It (点击打开题目链接)
题解博客:(点击打开题解链接)
线段树区间更新是难点,由于更新一段区间如果每个节点都去更新很容易超时,可以引入一个延迟变量(懒惰变量lazy),用来标记是否需要对子叶进行更改,再在下一次修改或查询时进行更新以减少操作降低时间。
区间更新题型:加上某值(add k) poj 3468 A Simple Problem with Integers (点击打开题目链接)
题意:有一段数,有两种操作,一:某一区间所有值加上某数。二:输出某一区间所有值的和。
区间更新题型:将某区间变为某数(set k) hduoj 1698 Just a Hook (点击打开链接)
题意:有n的数开始全为1,现在改变一段区间为1,2或3,进行m此操作,最后输出总长度。
hduoj 4027 Can you answer these queries? (点击打开链接)
题意:给出n个数和m次操作,操作有两种,1:将某个区间的所有值全部开方 2:输出区间值之和。由于开方,只能标记开方次数并且只有对最底层的子叶进行开方操作。
线段树最大连续区间:hduoj 1540 Tunnel Warfare (点击打开链接)
题意:给出n个数和m次操作,开始n个数可以全看为1并且长度都为n,有三种操作:D:破坏此村庄。Q:输出与此村庄联通的村庄数目。R:恢复最近一次破坏的村庄。区间长度在树中存下:此节点中最大的值sum,从左边界点向右延伸的点ll,从右边界点向左延伸的点rl。
小结:线段树的大概框架都有个模板,基本就是建立个完全二叉树,在树上查找更新。区别在于,每一棵树上需要维护的值和方式,问题不同,维护的值,数量和方式也不同,这才是需要关注的地方。(刚接触时的小结)
(待续。。。)
线段树单点更新,每次修改对应子叶节点,相应的根节点也要改变,如hduoj 1754 I Hate It (点击打开题目链接)
题解博客:(点击打开题解链接)
线段树区间更新是难点,由于更新一段区间如果每个节点都去更新很容易超时,可以引入一个延迟变量(懒惰变量lazy),用来标记是否需要对子叶进行更改,再在下一次修改或查询时进行更新以减少操作降低时间。
区间更新题型:加上某值(add k) poj 3468 A Simple Problem with Integers (点击打开题目链接)
题意:有一段数,有两种操作,一:某一区间所有值加上某数。二:输出某一区间所有值的和。
#include<stdio.h> #define LL long long int n,m; struct tree{ int l,r; LL v,m; }t[400100]; void build(int k,int l,int r){//构建线段树 t[k].l=l,t[k].r=r,t[k].m=0; if(l==r){ scanf("%lld",&t[k].v); return ; } int mid=(t[k].l+t[k].r)/2; build(k*2,l,mid); build(k*2+1,mid+1,r); t[k].v=t[k*2].v+t[k*2+1].v; } void pushup(int k){//子叶节点向上更新 t[k].v=t[k*2].v+t[k*2+1].v; } void pushdown(int k){//根节点标记向下更新 if(t[k].m){ t[k*2].m+=t[k].m; t[k*2].v+=t[k].m*(t[k*2].r-t[k*2].l+1); t[k*2+1].m+=t[k].m; t[k*2+1].v+=t[k].m*(t[k*2+1].r-t[k*2+1].l+1); t[k].m=0; } } LL query(int k,int l,int r){//查询某区间和函数 if(t[k].l>=l&&r>=t[k].r) return t[k].v; pushdown(k); int mid=(t[k].l+t[k].r)/2; LL t1=0,t2=0; if(mid>=l) t1=query(k*2,l,r); if(mid<r) t2=query(k*2+1,l,r); return t1+t2; } void updata(int k,int l,int r,LL add){//更新线段树 int mid=(t[k].l+t[k].r)/2; if(t[k].l>=l&&r>=t[k].r){//当前的线段在查询区间内,则此线段和增加(r-l+1)*add,并做上标记 t[k].m+=add; t[k].v+=(t[k].r-t[k].l+1)*add; return ; } pushdown(k); if(mid>=l) updata(k*2,l,r,add); if(mid<r) updata(k*2+1,l,r,add); pushup(k); } int main(){ scanf("%d%d",&n,&m); build(1,1,n); while(m--){ char str[10]; int l,r,x; scanf("%s",str); if(str[0]=='Q'){ scanf("%d%d",&l,&r); printf("%lld\n",query(1,l,r)); } if(str[0]=='C'){ scanf("%d%d%lld",&l,&r,&x); updata(1,l,r,x); } } return 0; }
区间更新题型:将某区间变为某数(set k) hduoj 1698 Just a Hook (点击打开链接)
题意:有n的数开始全为1,现在改变一段区间为1,2或3,进行m此操作,最后输出总长度。
#include<stdio.h> int n; struct tree{ int l,r,m,v; }t[400010]; void pushdown(int k){//向下传递将值置为add if(t[k].m){ t[k*2].m=t[k].m; t[k*2].v=(t[k*2].r-t[k*2].l+1)*t[k].m; t[k*2+1].m=t[k].m; t[k*2+1].v=(t[k*2+1].r-t[k*2+1].l+1)*t[k].m; t[k].m=0; } } void pushup(int k){ t[k].v=t[k*2].v+t[k*2+1].v; } void build(int k,int l,int r){ t[k].l=l,t[k].r=r,t[k].m=0; if(t[k].r==t[k].l){ t[k].v=1; return ; } int mid=(l+r)/2; build(k*2,l,mid); build(k*2+1,mid+1,r); pushup(k); } void updata(int k,int l,int r,int add){ if(t[k].l>=l&&t[k].r<=r){ t[k].m=add; t[k].v=add*(t[k].r-t[k].l+1); return ; } pushdown(k); int mid=(t[k].r+t[k].l)/2; if(mid>=l) updata(k*2,l,r,add); if(mid<r) updata(k*2+1,l,r,add); pushup(k); } int main(){ int m,ca,num=1; scanf("%d",&m); while(m--){ scanf("%d%d",&n,&ca); build(1,1,n); while(ca--){ int l,r,x; scanf("%d%d%d",&l,&r,&x); updata(1,l,r,x); } printf("Case %d: The total value of the hook is %d.\n",num++,t[1].v);//不需要询问,每次只要输出根节点的sum就行 } return 0; }
hduoj 4027 Can you answer these queries? (点击打开链接)
题意:给出n个数和m次操作,操作有两种,1:将某个区间的所有值全部开方 2:输出区间值之和。由于开方,只能标记开方次数并且只有对最底层的子叶进行开方操作。
#include<stdio.h> #include<math.h> #define LL long long int n; LL a[100010]; struct tree{ int l,r; LL v; }t[400100]; void build(int k,int l,int r){ t[k].l=l;t[k].r=r; if(l==r){ scanf("%lld",&t[k].v); return ; } t[k].v=0; int mid=(l+r)/2; build(k*2,l,mid); build(k*2+1,mid+1,r); t[k].v=t[k*2].v+t[k*2+1].v; } void updata(int k,int l,int r){ if(t[k].v==t[k].r-t[k].l+1) return ;//这一段全为1,开方操作不影响 if(t[k].l==t[k].r){ t[k].v=sqrt(double(t[k].v)); return ; } int mid=(t[k].l+t[k].r)/2; if(l<=mid) updata(k*2,l,r); if(r>mid) updata(k*2+1,l,r); t[k].v=t[k*2].v+t[k*2+1].v; } LL query(int k,int l,int r){ if(t[k].l>=l&&t[k].r<=r) return t[k].v; int mid=(t[k].l+t[k].r)/2; LL t1=0,t2=0; if(mid>=l) t1=query(k*2,l,r); if(mid<r) t2=query(k*2+1,l,r); return t1+t2; } int main(){ int ca=1; while(~scanf("%d",&n)){ int m; build(1,1,n); scanf("%d",&m); printf("Case #%d:\n",ca++); while(m--){ int x,i,j; scanf("%d%d%d",&x,&i,&j); if(i>j) {i+=j;j=i-j;i=i-j;} if(x==0){ updata(1,i,j); } else{ printf("%lld\n",query(1,i,j)); } } printf("\n"); } return 0; }
线段树最大连续区间:hduoj 1540 Tunnel Warfare (点击打开链接)
题意:给出n个数和m次操作,开始n个数可以全看为1并且长度都为n,有三种操作:D:破坏此村庄。Q:输出与此村庄联通的村庄数目。R:恢复最近一次破坏的村庄。区间长度在树中存下:此节点中最大的值sum,从左边界点向右延伸的点ll,从右边界点向左延伸的点rl。
#include<stdio.h> #include<algorithm> using namespace std; int n,m; int stk[50010],top;//用数组和top模拟栈,用来存下毁灭村庄的操作顺序 struct tree{ int l,r; int len,ll,rl; }t[200010]; void build(int k,int l,int r){ t[k].l=l,t[k].r=r; t[k].ll=t[k].rl=t[k].len=r-l+1;//开始时长度都为r-l+1 if(l==r) return ; int mid=(l+r)/2; build(k*2,l,mid); build(k*2+1,mid+1,r); } void updata(int k,int l,int add){//add为1时是修复操作,否则是毁灭操作 if(t[k].l==t[k].r){ if(add==1) t[k].ll=t[k].rl=t[k].len=1; else t[k].ll=t[k].rl=t[k].len=0; return ; } int mid=(t[k].r+t[k].l)/2; if(l<=mid) updata(k*2,l,add); else updata(k*2+1,l,add); if(t[k*2].ll==t[k*2].r-t[k*2].l+1) t[k].ll=t[k*2].ll+t[k*2+1].ll; else t[k].ll=t[k*2].ll; if(t[k*2+1].rl==t[k*2+1].r-t[k*2+1].l+1) t[k].rl=t[k*2+1].rl+t[k*2].rl;//更新节点的ll,rl else t[k].rl=t[k*2+1].rl; t[k].len=max(t[k*2].len,t[k*2+1].len);//根节点最大长度为子节点最大长度取max t[k].len=max(max(t[k].rl,t[k].ll),t[k].len);//若是左 4000 右孩子节点中间有连接部分,取max } int query(int k,int l){ if(t[k].l==t[k].r||t[k].len==0||t[k].len==t[k].r-t[k].l+1) return t[k].len; int mid=(t[k].l+t[k].r)/2; if(mid>=l){ if(l>=t[k*2].r-t[k*2].rl+1) return t[k*2].rl+t[k*2+1].ll;//如果查询编号在左右孩子节点的连接部分上,需要返回左rl+右ll else return query(k*2,l);//否则只在左孩子上 } else{ if(l<=t[k*2+1].l+t[k*2+1].ll-1) return t[k*2].rl+t[k*2+1].ll;//同理 else return query(k*2+1,l); } } int main(){ while(~scanf("%d%d",&n,&m)){ build(1,1,n); top=0; while(m--){ char str[10]; int x; scanf("%s",str); if(str[0]=='D'){ scanf("%d",&x); updata(1,x,-1); stk[top++]=x;//在stk中存下每次操作 } else if(str[0]=='Q'){ scanf("%d",&x); printf("%d\n",query(1,x)); } else{ x=stk[--top]; updata(1,x,1); } } } return 0; }
小结:线段树的大概框架都有个模板,基本就是建立个完全二叉树,在树上查找更新。区别在于,每一棵树上需要维护的值和方式,问题不同,维护的值,数量和方式也不同,这才是需要关注的地方。(刚接触时的小结)
(待续。。。)
相关文章推荐
- hdu 1003 Max Sum
- UIView release, message send to deallocated instance
- 跨源资源共享(CORS)
- iOS开发--用企业证书生成IPA包遇到的坑
- 监控WebSphere相关性能参数
- Java中break、continue与return的区别
- 【学习】js学习笔记:数组(二)
- 单个字符串多行显示
- 对uiview实现部分圆角
- CSU 1767: 想打架吗?算我一个!所有人,都过来!(2)
- RecyclerView实现横向的GridView效果
- Git远程操作详解
- 274. H-Index
- Android微信分享和邮件分享
- 什么是威胁情报以及它如何有助于识别安全威胁
- 从MySQL全库备份中恢复某个库和某张表
- poj 3278 Catch That Cow
- 【学习】js学习笔记:数组(一)
- IEEE 802
- AJax 跨域问题