线段树简介
来源:互联网 发布:ansible windows 编辑:程序博客网 时间:2024/06/08 11:43
要看lazy-tag的请直接跳到区间改值
算法用途
用处很多啊,主要是维护某一区间的值,比如最大值,区间和什么的。支持修改和查询操作。
算法思想
对于每个区间[l,r],令[l,(l+r)/2]为该区间的左儿子,[(l+r)/2+1,r]为该节点的右儿子。对于每个查询/修改,做完当前节点后继续做其子节点,若当前节点超过查询/修改操作,直接返回。查询和修改均为递归操作,单次查询/修改时间O(logn)。
算法关键
建树
区间怎么建树啊??
标号啊!
像二叉树那样给每个区间标个号,区间标号num的左右儿子分别为num*2和num*2+1,一直到该区间左右边界位置相等(l==r)。
然后递归建树啊!
先定义这样一个struct:
struct tree{ int l;//左端点 int r;//右端点 int sum;//当前节点权值};
然后开个数组存树:
tree t[MAXN*4+5];//要开4倍MAXN。
再递归建树即可:
//这里是区间求和void build(int l,int r,int num){//l为当前区间左端点,r为当前区间右端点,num为当前区间编号 t[num].l=l;//左端点赋值 t[num].r=r;//右端点赋值 if (l==r){//如果是叶子节点 t[num].sum=a[l];//它的总和即为它自己 return; } //分别向左右递归 build(l,(l+r)/2,num*2); build((l+r)/2+1,r,num*2+1); t[num].sum=t[num*2].sum+t[num*2+1].sum;//当前节点的值即为它的两个孩子节点的值之和}
单点修改操作
单点修改的话还是比较好理解的,从1号节点(就是区间[1,n])开始递归,先根据区间范围判断是递归左孩子还是右孩子,直到递归到要修改的节点,修改完毕后回溯,重新计算经过节点的值即可。
void change(int p,int w,int num){//p为要修改的点(区间上的),w为修改的值,num为当前区间的编号 if (t[num].l==t[num].r){//如果找到该节点 t[num].sum=w; return; } if (p<=(t[num].l+t[num].r)/2)//判断p在哪个区间内 change(p,w,num*2); else change(p,w,num*2+1); t[num].sum=t[num*2].sum+t[num*2+1].sum;//重新计算该节点的值}
查询
设我们要查询的区间为[l,r],对于当前区间,判断其是否仍有被查询区间覆盖的部分,若没有就返回。如果该区间被查询区间完全覆盖,直接返回该节点的值即可。
int search(int l,int r,int num){ if (t[num].l>r||t[num].r<l)//如果当前区间并没有被查询区间覆盖 return 0; if (t[num].l>=l&&t[num].r<=r)//如果当前区间完全被查询区间覆盖 return t[num].sum; return search(l,r,num*2)+search(l,r,num*2+1);}
模板
以hihocoder1077为例:
#include<cstdio>#include<cstring>#include<algorithm>#define MAXN 1000000using namespace std;struct tree{ int l; int r; int sum;};int n,m;tree t[MAXN*4+5];int a[MAXN+5];void build(int l,int r,int num){ t[num].l=l; t[num].r=r; if (l==r){ t[num].sum=a[l]; return; } build(l,(l+r)/2,num*2); build((l+r)/2+1,r,num*2+1); t[num].sum=t[num*2].sum+t[num*2+1].sum;}void change(int p,int w,int num){ if (t[num].l==t[num].r){ t[num].sum=w; return; } if (p<=(t[num].l+t[num].r)/2) change(p,w,num*2); else change(p,w,num*2+1); t[num].sum=t[num*2].sum+t[num*2+1].sum;}int search(int l,int r,int num){ if (t[num].l>r||t[num].r<l) return 0; if (t[num].l>=l&&t[num].r<=r) return t[num].sum; return search(l,r,num*2)+search(l,r,num*2+1);}int main(){ scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%d",&a[i]); build(1,n,1); scanf("%d",&m); for (int i=1;i<=m;i++){ int flag,x,y; scanf("%d%d%d",&flag,&x,&y); if (!flag) printf("%d\n",search(x,y,1)); else change(x,y,1); } return 0;}
区间修改
很多时候题目并不只限于单点修改,而是需要修改某一个连续区间的值,这时如果和上面一样做的话,单次修改的复杂度为O(L*logn)(L为区间长度),还不如直接暴力O(L)修改。这个时候我们就要用到一个神奇的东西:Lazy-Tag。
Lazy-Tag,懒惰标记,对于修改[l,r],如果当前区间被完全覆盖,我们就不要推下去,而是懒懒地给这个区间打上标记,并更新它的权值。一般标记内容就是修改的值。这样就可以免去之后的工作。
像这样:
if (t[num].l>=l&&t[num].r<=r){//如果当前区间被完全覆盖 lazy[num]=w;//打上标记 t[num].sum=(t[num].r-t[num].l+1)*lazy[num];//更新权值(这里是把一段区间的值都改为w,询问区间和) return;}
但是查询的时候,或者要修改它子区间的值的时候怎么办呢?
当我们做到一个节点时,立即把懒惰标记传递给他的儿子,并更新它的权值。这样就可以保证查询/修改过的都是更新过的。这个操作可以写一个函数来实现。
void pushdown(int num){ lazy[num*2]=lazy[num*2+1]=lazy[num];//把标记传给子节点 t[num*2].sum=(t[num*2].r-t[num*2].l+1)*lazy[num*2];//更新权值 t[num*2+1].sum=(t[num*2+1].r-t[num*2+1].l+1)*lazy[num*2+1]; lazy[num]=0;//清零}
然后我们就可以写出查询和修改的代码了:
void change(int l,int r,int w,int num){//修改 if (t[num].l>r||t[num].r<l) return; if (t[num].l>=l&&t[num].r<=r){//如果区间被完全覆盖 lazy[num]=w; t[num].sum=(t[num].r-t[num].l+1)*lazy[num]; return; } if (lazy[num]) pushdown(num); change(l,r,w,num*2); change(l,r,w,num*2+1); t[num].sum=t[num*2].sum+t[num*2+1].sum;}
int search(int l,int r,int num){//查询 if (t[num].l>r||t[num].r<l) return 0; if (t[num].l>=l&&t[num].r<=r) return t[num].sum; if (lazy[num]) pushdown(num); return search(l,r,num*2)+search(l,r,num*2+1);}
模板
以hihocoder1078为例:
#include<cstdio>#include<cstring>#include<algorithm>#define MAXN 100000using namespace std;struct tree{ int l; int r; int sum;};int n,m;tree t[4*MAXN+5];int a[MAXN+5],lazy[4*MAXN+5];void build(int l,int r,int num){ t[num].l=l; t[num].r=r; if (l==r){ t[num].sum=a[l]; return; } build(l,(l+r)/2,num*2); build((l+r)/2+1,r,num*2+1); t[num].sum=t[num*2].sum+t[num*2+1].sum;}void pushdown(int num){ lazy[num*2]=lazy[num*2+1]=lazy[num]; t[num*2].sum=(t[num*2].r-t[num*2].l+1)*lazy[num*2]; t[num*2+1].sum=(t[num*2+1].r-t[num*2+1].l+1)*lazy[num*2+1]; lazy[num]=0;}void change(int l,int r,int w,int num){ if (t[num].l>r||t[num].r<l) return; if (t[num].l>=l&&t[num].r<=r){ lazy[num]=w; t[num].sum=(t[num].r-t[num].l+1)*lazy[num]; return; } if (lazy[num]) pushdown(num); change(l,r,w,num*2); change(l,r,w,num*2+1); t[num].sum=t[num*2].sum+t[num*2+1].sum;}int search(int l,int r,int num){ if (t[num].l>r||t[num].r<l) return 0; if (t[num].l>=l&&t[num].r<=r) return t[num].sum; if (lazy[num]) pushdown(num); return search(l,r,num*2)+search(l,r,num*2+1);}int main(){ scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%d",&a[i]); build(1,n,1); scanf("%d",&m); for (int i=1;i<=m;i++){ int flag; scanf("%d",&flag); if (!flag){ int x,y; scanf("%d%d",&x,&y); printf("%d\n",search(x,y,1)); } else{ int x,y,w; scanf("%d%d%d",&x,&y,&w); change(x,y,w,1); } } return 0;}
- 线段树简介(转)
- 线段树简介
- 线段树简介
- 线段树简介
- 线段树简介
- 线段树简介
- 线段树(Segment Tree)简介
- 见微知著----POJ2182(线段树简介)
- 线段树?线段树!
- 线段树?线段树!
- 线段_线段树
- 线段_线段树
- 主席树/可持久化线段树简介(洛谷P3834/P3919)
- 【给将来学神的算法详解--数据结构--线段树】(1)简介
- 线段树
- 线段树
- 线段树
- 线段树
- 文件I/O与标准I/O
- 用开源代码构建机器人需要考虑的问题
- JavaScript中引用类型详解
- walle web部署系统工具踩坑
- 五、改进神经网络的学习方法(1):交叉熵代价函数
- 线段树简介
- ViewPager,XListView主Activity
- 事件深入应用一
- 【Real-Time Rendering 3rd】开坑的序章
- 大数据量下高并发同步的讲解
- 基于Android真实项目教你一步一步搭建架构1 -- 概述
- Java基础加强-读取配置文件和内省
- go语言安装第三方程序包
- kafka集群编程指南