Kai’blog

主博客 | 势利纷华,近之而不染者洁,不近者亦洁,君子不立危墙之下。

数据结构【线段树】

对于一个数据结构而言,我们总要能对其进行两件事:修改和操作。操作在这里是一个专有名词,专门指代求最值、求和等操作,具体能指代什么操作之后再聊。

 

如果朴素的用数组进行存储,那么修改是O(1)的,而操作往往是O(n)的。

当操作指的是求和的时候,我们可以使用前缀和算法,前缀和使得操作是O(1)的,然而,前缀和同时也使得修改是O(n)的。

对于其他操作,也有类似于前缀和一样的较为简单的做法使得操作变成一个O(1)的过程,但他们却也同样会这的修改变成了一个O(n)的过程,我们讲包括前缀和本身在内的这些朴素优化方法统称为类前缀和做法。

 

如果我们要同时进行大量修改和操作,那么不管是朴素数组法还是类前缀和做法,总的时间复杂度都是O(n^2)的,那么有没有什么办法可以简化这一点呢?有的,线段树。线段树可以使得操作和修改都变成O(logn)的过程,虽然O(logn)比不过O(1),但是看总时间复杂度时,O(nlogn)相比于O(n^2)可谓是遥遥领先,这便是这一数据结构的优点。

 

至于树状数组,则是线段树的特化版,节约了75%的空间与90%的时间,牺牲了适用范围的结果,详见我的《树状数组》一文

 

这里继续介绍线段树

 

线段树的核心思想在于把原序列用递归式的二分法化成多个区间,从而形成一个二叉树,树的每个非叶子节点都对应着一个区间,叶节点则对应着原序列的特定元素。每一个节点代表着的是一个区间,存储的内容既包括区间的左右端点,还包括与这区间有关相关数据。比如如果操作指的是求和,那么存储的内容便是区间元素的和。

 

建树的过程相当于是在预处理信息,是一个O(n)的过程,单点修改和区间查询都是一个递归的O(logN)过程,,区间修改则由于懒惰标记的引入,也是一个O(logN)的过程,当有多个lazytag的时候,写一下就会发现按照先乘后加来写更容易。

代码如下

 1 const int MaxN=100000+5;
 2 int a[MaxN],Nmax=-1;
 3 struct node
 4 {
 5     int l,r,sum,add,mul;
 6     node(){l=r=sum=add=0,mul=1;}
 7     node(int L,int R,int SUM,int ADD,int MUL){l=L;r=R;sum=SUM;add=ADD;mul=MUL;}
 8 }tr[MaxN*4];
 9 //x是节点编号,根据完全二叉树的性质可知,x节点的左右子节点编号为x*2,x*2+1
10 void _build(int x,int l,int r)
11 {
12     tr[x].l=l,tr[x].r=r;//节点表示区间的左右界    
13     if(l==r)
14     {
15         //若l=r,说明这个节点是叶子节点,直接赋值        
16         tr[x].sum=a[l];//a是原数列   
17         Nmax=max(Nmax,x);     
18         return;
19     }
20     int mid=(l+r)/2;//mid表示左右子区间的间隔            
21     _build(x<<1,l,mid),_build((x<<1)+1,mid+1,r);//递归建树   
22     tr[x].sum=(tr[x<<1].sum+tr[(x<<1)+1].sum)%M;//pushup操作
23 }
24 inline void build(int r){_build(1,1,r);}
25 //区间查询
26 inline void pushdown(int now)
27 {    //先乘后加
28     //(x*a+b)*c+d=x*ac+bc+d
29     if(tr[now].add==0&&tr[now].mul==1)return;
30 
31     tr[now<<1].mul=tr[now<<1].mul*tr[now].mul%M;
32     tr[(now<<1)+1].mul=tr[(now<<1)+1].mul*tr[now].mul%M;
33 
34     tr[now<<1].add=(tr[now<<1].add*tr[now].mul+tr[now].add)%M;
35     tr[(now<<1)+1].add=(tr[(now<<1)+1].add*tr[now].mul+tr[now].add)%M;
36 
37     tr[now<<1].sum=(tr[now<<1].sum*tr[now].mul+tr[now].add*(tr[now<<1].r-tr[now<<1].l+1))%M;
38     tr[(now<<1)+1].sum=(tr[(now<<1)+1].sum*tr[now].mul+tr[now].add*(tr[(now<<1)+1].r-tr[(now<<1)+1].l+1))%M;
39 
40     tr[now].add=0;tr[now].mul=1;
41 }
42 int _query(int x,int l,int r)
43 {
44     if(tr[x].l>=l&&tr[x].r<=r) return tr[x].sum;//如果该节点的区间被要查找的区间包括了,那么就不用继续找了,直接返回改节点的值就行了
45     pushdown(x);
46     int mid=(tr[x].l+tr[x].r)>>1;
47     int sum=0;
48     if(l<=mid&&r>=tr[x].l) sum+=_query(x<<1,l,r);//如果当前节点在要查找区间左边界的右面,那么递归查找左子树
49     if(r>=mid+1&&l<=tr[x].r) sum+=_query((x<<1)+1,l,r);//如果当前节点在要查找区间右边界的左面,那么递归查找右子树
50     return sum;//由此得出了该区间的值,返回即可
51 }
52 inline int query(int l,int r){return _query(1,l,r);}
53 //单点修改
54 void _change(int now,int x,int k)
55 {
56     if(tr[now].l==tr[now].r){tr[now].sum=k;return;}
57     pushdown(x);
58     int mid=(tr[now].l+tr[now].r)>>1;
59     if(x<=mid)_change((now<<1),x,k);
60     else _change((now<<1)+1,x,k);
61     tr[now].sum=(tr[now<<1].sum+tr[(now<<1)+1].sum)%M;
62 }
63 inline void change(int x,int k){_change(1,x,k);}
64 //某节点被打了懒惰标记表示它本身修改了,但他的子节点尚未修改
65 void _update(int now,int l,int r,int k)
66 {
67     if(r<tr[now].l||l>tr[now].r)return;
68     if(l<=tr[now].l&&tr[now].r<=r)
69     {
70         tr[now].sum=(tr[now].sum+k*(tr[now].r-tr[now].l+1))%M;//先修改这个区间
71         tr[now].add=(tr[now].add+k)%M;//然后给它打上懒标记
72         return;
73     }
74     pushdown(now);
75     int mid=(tr[now].l+tr[now].r)/2;
76     if(l<=mid&&r>=tr[now].l)_update(now<<1,l,r,k);
77     if(r>=mid+1&&l<=tr[now].r)_update((now<<1)+1,l,r,k);
78     tr[now].sum=(tr[now<<1].sum+tr[(now<<1)+1].sum)%M;
79     //如果是叶节点,标记会被下传到不会被访问到的虚空节点去,便相当于是消除标记了,因而无需单独特判叶节点
80 }
81 void __update(int now,int l,int r,int k)
82 {
83     if(r<tr[now].l||l>tr[now].r)return;
84     if(l<=tr[now].l&&tr[now].r<=r)
85     {
86         tr[now].sum=tr[now].sum*k%M;//先修改这个区间
87         tr[now].add=tr[now].add*k%M;//然后给它打上lazytag并更新lazytag
88         tr[now].mul=tr[now].mul*k%M;
89         return;
90     }
91     pushdown(now);
92     int mid=(tr[now].l+tr[now].r)/2;
93     if(l<=mid&&r>=tr[now].l)__update(now<<1,l,r,k);
94     if(r>=mid+1&&l<=tr[now].r)__update((now<<1)+1,l,r,k);
95     tr[now].sum=(tr[now<<1].sum+tr[(now<<1)+1].sum)%M;
96     //如果是叶节点,标记会被下传到不会被访问到的虚空节点去,便相当于是消除标记了,因而无需单独特判叶节点
97 }
98 void updateofadd(int l,int r,int k){_update(1,l,r,k);}
99 void updateofmul(int l,int r,int k){__update(1,l,r,k);}
View Code

 

 
 
 
posted @ 2024-02-28 09:35  Kai-G  阅读(4)  评论(0编辑  收藏  举报
Copyright © 2019-2020 拱垲. All rights reserved.