• 暑假集训--线段树


    线段树

    • 线段树的每个结点都代表一个区间。
    • 线段树有唯一的根节点代表整个范围,比如:[1,N];
    • 线段树的每个叶子结点都代表一个长度为1的元区间 [x,x];
    • 对于每个内部节点[l,r],它的左节点是[l,m],右节点是[m+1,r],其中m=(l+r)/2(向下取整)
    • 图例说明:

     

       该线段树存储的是[0,7]区间的相应信息。

    • 可以得出线段树大约有2n个结点,深度为O(logn)
    • 一般采用数组或结构体的方式存储,编号为i的结点的左儿子为2*i,右儿子为2*i+1
    • 每个结点维护对应区间的和
    • 建树(这里采用了结构体形式)
     1 struct SegmentTree
     2 {
     3     int l, r;
     4     int data;
     5 } t[N * 4 + 5];
     6 int a[N];  //原始数据数组
     7 void build(int p,int l,int r)  //存储区间和的线段树
     8 {
     9     t[p].l = l, t[p].r = r;  //节点p代表[l,r]
    10     if(l==r) //单点
    11     {
    12         t[p].data = a[l]; 
    13         return;
    14     }
    15     int m = (l + r) / 2;  //折半
    16     int ls = 2 * p, rs = ls + 1;
    17     build(ls, l, m);      //建左子树
    18     build(rs, m + 1, r);  //建右子树
    19     t[p].data = t[ls].data + t[rs].data;   //p代表的区间和 = ls代表的区间和 + rs代表的区间和
    20 }
    View Code
     1 void build(int p,int l,int r)  //存储区间最大值的线段树
     2 {
     3     t[p].l = l, t[p].r = r;
     4     if(l==r) 
     5     {
     6         t[p].date = a[l];
     7         return;
     8     }
     9     int m = (l + r) / 2;
    10     int ls = 2 * p, rs = ls + 1;
    11     build(ls, l, m);
    12     build(rs, m + 1, r);
    13     t[p].date = max(t[ls].date,t[rs].date);  //p区间的最大值 = max(ls最大值,rs最大值)
    14 }
    View Code

    第二段代码需要注意一点:ls和rs为p的两个子区间,故可以通过 t[p].date = max(t[ls].date,t[rs].date);得出p区间的最大值,但是不可以通过ls和p求出rs区间的最大值。

    • 修改

      参考上图,修改只需要修改对应点以及他的所有祖先即可,复杂度和深度一样为O(logn)

     1 void add(int p,int x,int v)  //将x位置的数增加v  同样是存储区间和的线段树
     2 {
     3     if(t[p].l==t[p].r)
     4     {
     5         t[p].data += v;
     6         return;
     7     }
     8     int m = (t[p].l + t[p].r) / 2;
     9     int ls = 2 * p, rs = ls + 1;
    10     if(x<=m)
    11         add(ls, x, v);
    12     else
    13         add(rs, x, v);
    14     t[p].data = t[ls].data + t[rs].data;
    15 }
    View Code
     1 void add(int p,int x,int v)  //维护区间最大值的线段树  将x位置的数值改为v
     2 {
     3     if(t[p].l==t[p].r)
     4     {
     5         t[p].date = v;
     6         return;
     7     }
     8     int m = (t[p].l + t[p].r) / 2;
     9     int ls = 2 * p, rs = ls + 1;
    10     if(x<=m)
    11         add(ls, x, v);
    12     else
    13         add(rs, x, v);
    14     t[p].date = max(t[ls].date,t[rs].date);
    15 }
    View Code
    • 查询

     

       如图:若要查询区间[1,7]的和,则需要把他分成[1,1]、[2,3]、[4,7]三段连续的区间即可

       因为查询的是连续的区间所以最多分解为O(logn)个区间

     

     1 int Query(int p,int l,int r)  //询问l-r区间和
     2 {
     3     if(l<=t[p].l&&r>=t[p].r) //l-r完全覆盖了p代表的区间
     4     {
     5         return t[p].data;  //直接返回值
     6     }
     7     int m = (t[p].l + t[p].r) / 2;   //向下取整
     8     int ls = 2 * p, rs = ls + 1;
     9     int sum = 0;
    10     if(l<=m)  //p此时的区间的左半边和l-r有交集但不完全被完全覆盖
    11     {
    12         sum += Query(ls, l, m); //查询左半边 此时的l-r其实是l-m
    13     }
    14     if(r>m)   //这里没有等于
    15     {
    16         sum += Query(rs, m + 1, r);  //同理
    17     }
    18 }
    View Code

     

     1 int Query(int p,int l,int r)  //询问区间最大值
     2 {
     3     if(l<=t[p].l&&r>=t[p].r)
     4     {
     5         return t[p].date;
     6     }
     7     int m = (t[p].l + t[p].r) / 2;
     8     int ls = 2 * p, rs = ls + 1;
     9     int maxx=-inf;  //初始化最大值为-inf
    10     if(l<=m)
    11            maxx=max(maxx,Query(ls,l,r));
    12     if(r>m)
    13         maxx=max(maxx,Query(rs,l,r));
    14     return maxx;
    15 }
    View Code

      以上是线段树的基本操作,数组存储方式的代码实现这里就不贴了,道理相同掌握了一个另一个自然可以写出来

    • Pushdown(延迟标记)

      简单来说延迟标记的主要思想就是:如果在执行“区间修改”这个指令时,发现某个区间被修改区间全部覆盖,则以该结点为根的子树的所有结点都应该被修改,若进行逐一更新复杂度会提升,故在回溯之前向该节点p增加一个标记:标识“该节点曾经被修改,但其子节点尚未被更新”。

      如果在后续的指令中,需要从结点p向下递归,则再检查p是否具有标记,若有标记,就根据标记信息更新p的两个子结点,同时为p的两个子节点增加标记,然后清除p的标记。

      也就是说,除了修改指令中划分的O(logn)个结点之外,对任意结点的修改都延迟到“在后续操作中递归进入他的父节点时”再执行。这些标记被成称为“延迟标记”。

      下面是蓝书上Pushdown的模板

     1 struct SegmentTree
     2 {
     3     int l, r;
     4     ll sum, add;   //add为增量延迟标记
     5     #define l(x) t[x].l
     6     #define r(x) t[x].r
     7     #define sum(x) t[x].sum
     8     #define add(x) t[x].add
     9 } t[N * 4];
    10 int a[N], n, m;
    11 void build(int p,int l,int r)
    12 {
    13     l(p) = l, r(p) = r;
    14     if(l==r)
    15     {
    16         sum(p) = a[l];
    17         return;
    18     }
    19     int m = (l + r) / 2;
    20     int ls = 2 * p, rs = 2 * p + 1;
    21     build(ls, l, m);
    22     build(rs, m + 1, r);
    23     sum(p) = sum(ls) + sum(rs);
    24 }
    25 void spread(int p)
    26 {
    27     if(add(p))
    28     {
    29         int ls = 2 * p, rs = 2 * p + 1;
    30         sum(ls) += add(p) * (r(ls) - l(ls) + 1);
    31         sum(rs) += add(p) * (r(rs) - l(rs) + 1);
    32         add(ls) += add(p);
    33         add(rs) += add(p);
    34         add(p) = 0;
    35     }
    36 }
    37 void change(int p,int l,int r,int d)
    38 {
    39     if(l<=l(p)&&r>=r(p))
    40     {
    41         sum(p) += (ll)d * (r(p) - l(p) + 1);
    42         add(p) += d;
    43         return;
    44     }
    45     spread(p);
    46     int m = (l(p) + r(p)) / 2;
    47     int ls = 2 * p, rs = 2 * p + 1;
    48     if(l<=m)
    49         change(ls, l, r, d);
    50     if(r>m)
    51         change(rs, l, r, d);
    52     sum(p) = sum(ls) + sum(rs);
    53 }
    54 ll ask(int p,int l,int r)
    55 {
    56     if(l<=l(p)&&r>=r(p))
    57         return sum(p);
    58     spread(p);
    59     int m = (l(p) + r(p)) / 2;
    60     int ls = 2 * p, rs = 2 * p + 1;
    61     ll val = 0;
    62     if(l<=m)
    63         val += ask(ls, l, r);
    64     if(r>m)
    65         val += ask(rs, l, r);
    66     return val;
    67 }
    View Code

      了解了延迟标记之后则可以用线段树处理区间修改,区间查询的问题 

      https://www.luogu.org/problemnew/show/P3372

    (未完

     

  • 相关阅读:
    ubuntu更换阿里源
    记一次开源软件的篡改
    linux下搜索指定内容
    随笔_1
    单细胞中的细胞类型划分
    scDNA-seq genomic analysis pipline
    NIH周三讲座视频爬虫
    ggplot2_bubble
    TCGA数据批量下载
    lncRNA芯片重注释
  • 原文地址:https://www.cnblogs.com/zssst/p/11069484.html
Copyright © 2020-2023  润新知