线段树

来源:互联网 发布:马里亚纳网络为何恐怖 编辑:程序博客网 时间:2024/06/05 17:46

  • 线段树是什么
  • 怎么构建
  • 模板
  • 例题

该来的还是要来的.理解了树状数组之后想要解决区间更新及询问的问题,就要用到线段树.当然非常骚的树状数组可以过,不过那就是大佬做的事了.线段树其实不是一个很难的东西,因为递归二分的原因,除了push_down操作比较费事,其它都很好背下来.只要理解一点点就好.

线段树是什么

先引入一个题:洛谷模板题3372.
就是给一个数组,有两种操作:给一个区间的每个数加k,或让你求一个区间的和.
暴力! n*m,gg.那肯定不行.但是用线段树可以.它的预处理复杂度nlogn,操作复杂度logn,空间复杂度n,就能过了.
我再一次作为灵魂画师为大家讲解一下线段树.假设有一个数组a[11],存储数据的位置为1-10.
这里写图片描述

我又画得很骚,不要在意.
也就是说线段树就是一棵完全二叉树,它的每个叶子结点存储数组元素的值,而它的父节点是两个子节点的和.一共有2*n-1个节点,所以处理的时候数组要用2xN的大小,甚至有些时候要开到4xN.

怎么构建

好的.首先我们先建立线段树.可以知道,每一棵子树根节点是segtree[i],左孩子是segtree[i*2],右孩子是segtree[i*2+1].我们可以用位运算加速计算左孩子和右孩子.
据此可写出函数build.

#define mid ((istart+iend)>>1)#define lson root<<1,istart,mid#define rson root<<1|1,mid+1,iendstruct node{ll value,lazy;}segtree[boss*4+10];void build(ll a[],int root,int istart,int iend){segtree[root].lazy=0;//先不要管这个lazy是什么,只看下面if (istart==iend) segtree[root].value=a[istart];else   {  build(a,lson),build(a,rson);//建立左孩子,建立右孩子  segtree[root].value=segtree[root<<1].value+segtree[root<<1|1].value;  }}

不会也不要紧,背出之.
然后是询问操作.

ll query(int root,int istart,int iend,int qstart,int qend){if (qstart>iend||qend<istart) return 0;//qstart,qend如果不在范围内询问失败if (qstart<=istart&&qend>=iend) return segtree[root].value;//在范围内,询问出根节点的值push_down(root,istart,iend);return query(lson,qstart,qend)+query(rson,qstart,qend);//递归左孩子右孩子}

诶?这个push_down是什么玩意?

inline void push_down(int root,int istart,int iend)//先不解释.{if (segtree[root].lazy!=0)  {  segtree[root<<1].lazy+=segtree[root].lazy;  segtree[root<<1|1].lazy+=segtree[root].lazy;  segtree[root<<1].value+=segtree[root].lazy*(mid-istart+1);  segtree[root<<1|1].value+=segtree[root].lazy*(iend-mid);  segtree[root].lazy=0;  }}

接下来才是高潮呢!终于要解释lazy是什么了.
为了更新区间,线段树中引入了延迟标记这个概念.这才是线段树的精华所在.
每个节点新增加一个标记,记录这个节点是否进行了某种修改(这种修改操作会影响其子节点),对于任意区间的修改,我们先按照区间查询的方式将其划分成线段树中的节点,然后修改这些节点的信息,并给这些节点标记上代表这种修改操作的标记。在修改和查询的时候,如果我们到了一个节点p,并且决定考虑其子节点,那么我们就要看节点p是否被标记,如果有,就要按照标记修改其子节点的信息,并且给子节点都标上相同的标记,同时消掉节点p的标记。
区间更新举例说明:当我们要对区间[0,2]的叶子节点增加2,利用区间查询的方法从根节点开始找到了非叶子节点[0-2],把它的值设置为1+2 = 3,并且把它的延迟标记设置为2,更新完毕;当我们要查询区间[0,1]内的最小值时,查找到区间[0,2]时,发现它的标记不为0,并且还要向下搜索,因此要把标记向下传递,把节点[0-1]的值设置为2+2 = 4,标记设置为2,节点[2-2]的值设置为1+2 = 3,标记设置为2(其实叶子节点的标志是不起作用的,这里是为了操作的一致性),然后返回查询结果:[0-1]节点的值4;当我们再次更新区间[0,1](增加3)时,查询到节点[0-1],发现它的标记值为2,因此把它的标记值设置为2+3 = 5,节点的值设置为4+3 = 7;
//此处摘至http://blog.sina.com.cn/s/blog_a2dce6b30101l8bi.html
这样的话更新操作时,当父节点的延迟标记不为0,我们可以把它传到子节点,然后消掉父节点的标记,直到变成单点修改,标记消失.非常神奇吧?
解释完了以后就可以看区间更新了.

void update(int root,int istart,int iend,int ustart,int uend,int k){if (ustart>iend||uend<istart) return;if (ustart<=istart&&uend>=iend)   {  segtree[root].lazy+=k;  segtree[root].value+=k*(iend-istart+1);  return;  }push_down(root,istart,iend);update(lson,ustart,uend,k);update(rson,ustart,uend,k);segtree[root].value=segtree[root<<1].value+segtree[root<<1|1].value;}//应该不需要我再说明了.

模板

最后是AC洛谷3372模板的代码.

#include<stdio.h>#define boss 100000#define mid ((istart+iend)>>1)#define lson root<<1,istart,mid#define rson root<<1|1,mid+1,iendusing namespace std;typedef long long ll;struct node{ll value,lazy;}segtree[boss*4+10];ll a[boss+10];int n,m,t,x,y,k; void build(ll a[],int root,int istart,int iend){segtree[root].lazy=0;if (istart==iend) segtree[root].value=a[istart];else   {  build(a,lson),build(a,rson);  segtree[root].value=segtree[root<<1].value+segtree[root<<1|1].value;  }}inline void push_down(int root,int istart,int iend){if (segtree[root].lazy!=0)  {  segtree[root<<1].lazy+=segtree[root].lazy;  segtree[root<<1|1].lazy+=segtree[root].lazy;  segtree[root<<1].value+=segtree[root].lazy*(mid-istart+1);  segtree[root<<1|1].value+=segtree[root].lazy*(iend-mid);  segtree[root].lazy=0;  }}ll query(int root,int istart,int iend,int qstart,int qend){if (qstart>iend||qend<istart) return 0;if (qstart<=istart&&qend>=iend) return segtree[root].value;push_down(root,istart,iend);return query(lson,qstart,qend)+query(rson,qstart,qend);}void update(int root,int istart,int iend,int ustart,int uend,int k){if (ustart>iend||uend<istart) return;if (ustart<=istart&&uend>=iend)   {  segtree[root].lazy+=k;  segtree[root].value+=k*(iend-istart+1);  return;  }push_down(root,istart,iend);update(lson,ustart,uend,k);update(rson,ustart,uend,k);segtree[root].value=segtree[root<<1].value+segtree[root<<1|1].value;}int main(){int i;scanf("%d%d",&n,&m);for (i=1;i<=n;i++) scanf("%lld",&a[i]);build(a,1,1,n);for (i=1;i<=m;i++)  {  scanf("%d",&t);  if (t==1)    {    scanf("%d%d%d",&x,&y,&k);    update(1,1,n,x,y,k);    }  else if (t==2)    {    scanf("%d%d",&x,&y);    printf("%lld\n",query(1,1,n,x,y));    }  }}

在写这篇博客之前我也不知道延迟标记到底是什么,当我解释完了之后发现自己会了.

例题

我看看有没有线段树的例题.有几个.
我再引用一下别人的博客吧.
线段树经典例题

原创粉丝点击