暑假集训--线段树

Posted zstofljq

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了暑假集训--线段树相关的知识,希望对你有一定的参考价值。

线段树

  • 线段树的每个结点都代表一个区间。
  • 线段树有唯一的根节点代表整个范围,比如:[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

(未完

 

 

以上是关于暑假集训--线段树的主要内容,如果未能解决你的问题,请参考以下文章

暑假集训8.7数据结构专题-线段树存直线

暑假集训-8.14总结

暑假集训8.7数据结构专题-很妙的线段树( 觉醒力量(hidpower))

2018暑假集训第五周感想

暑假集训test-8-30

2017暑假集训前总结和规划