浅谈线段树Segment_Tree
By xiaruize
 引言
OI中,有一种好玩的游戏,叫做码线段树,那么线段树是什么???
 线段树的目的
线段树主要用于在区间上动态维护一些值(如最大值,最小值,和,积等)
 线段树的实现
以区间最大值为例,给定一个长度为 n 的数组,每次查询求 [l,r] 之间的最大值,或将 [l,r] 赋值为 val
显然,如果暴力去维护这个问题,需要 O(NQ) 的时间复杂度, 并不能解决1≤n,q≤105级别的问题
此时,我们考虑将原问题分解为 ([l,mid] 的 max 与 [mid+1,r] 的 max) 的 max
接着,我们可以以此类推,细分到只有一个节点
当 n=6 时, 线段树如下图

 线段树的存储
一般线段树使用和二叉树类似的方式存储注意4倍空间
这样存储的好处在于,对于点p, 它的左儿子是$ p<<1 $, 右儿子是 p<<1∣1, 父节点是 p>>1
建议使用define定义ls,rs为左右儿子
 上传pushup
该操作用于通过p节点的儿子更新p节点
以求最大值为例
1 2 3 4
   | void pushup(int p) {     seg[p].val=max(seg[ls].val,seg[rs].val); }
   | 
 建树 build
首先,肯定需要建树,递归即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | void build(int l,int r,int p) {     seg[p].l=l;     seg[p].r=r;      if(l==r)      {         seg[p].val=a[l];          return;     }     int mid=(l+r)>>1;      build(l,mid,ls);      build(mid+1,r,rs);      pushup(p);  }
   | 
也可以将当前的区间放在参数里,主要看个人习惯
 修改update
 单点修改
如果只需要修改一个点,那么可以通过 l,r 的指引, dfs 到这个点,修改它的值,返回时进行一遍 pushup
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | void update(int p,int d,int val) {     if(seg[p].l==seg[p].r)      {         seg[p].val=d;         return;     }     int mid=seg[p].l+seg[p].r>>1;     if(d<=mid)         update(ls,d,val);     if(d>mid)         update(rs,d,val);     pushup(p);  }
   | 
 区间修改(lazy tag)
我们在回头看一下题目,发现需要支持区间修改,如果做n次单点修改,会消耗大量的时间
此时,我们可以在一段在子节点都需要修改的节点上打一个懒标记(lazy tag),表示当前节点及下属的节点都需要修改
例如,修改区间[1,5]为2

这样,可以用更优的速度完成修改操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | void update(int u, int l, int r, int d) {     if (tr[p].l >= l && tr[p].r <= r)       {         tr[p].val=max(tr[u].val,d);         tr[p].laz=max(tr[u].laz,d);     }     else     {         pushdown(p);          int mid = tr[p].l + tr[p].r >> 1;         if (l <= mid) update(ls, l, r, d);          if (r > mid) update(rs, l, r, d);          pushup(p);      } }
   | 
 下传懒标记pushdown
如果你认真阅读了上面的代码,会发现pushdown函数并没有讲过
那么pushdown的作用其实是下传懒标记
及将父亲节点记录的修改(laz)传给儿子
1 2 3 4 5 6 7 8
   | void pushdown(int p) {     seg[ls].laz=seg[p].laz;     seg[ls].val=seg[p].laz;     seg[rs].laz=seg[p].laz;     seg[rs].val=seg[p].laz;     seg[p].laz=0; }
   | 
 查询 query
那么,已经有了一棵线段树,要如何查询区间最值呢?
可以去模仿update操作,每次询问左区间和右区间,在合并答案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | int query(int l,int r,int ll,int rr,int p)  {     if(l>=ll&&r<=rr)     {         return seg[p].val;      }     int mid=(l+r)>>1;     int res=0;     pushdown(p);     if(mid<rr)         res=max(res,query(mid+1,r,ll,rr,rs));      if(mid>=ll)         res=max(res,query(l,mid,ll,rr,ls));      return res; }
   | 
读到这里,你应该已经可以基本掌握如何码线段树. 但是,要在码线段树的游戏中超过你的同伴,你需要多加练习