线段树

发布时间 2023-10-04 00:30:13作者: kkk05

前言:

继树状数组,一年后,kkk05终于决定把它的好兄弟——线段树附上。(明明一年前就学了但是今天才写的屑)

先简单地引入一下:P3372

忽略这道题的标题,看到这道题的第一想法大约是一维数组+m个循环+输出,照着这个思路写下去,不出意外的话,你将获得TLE。这时我们就该抬头看看标题:哦,什么是线段树呢?

线段树:

1)相关简介:

线段树是算法竞赛中常用的用来维护区间信息的数据结构。

线段树可以在O(logN)的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作,除了查询最值,它和树状数组几乎是互通的;并且,它是一种二叉搜索树。

如下图,就是一颗 [ 1,10 ] 的线段树:

其中,最上面那个是根节点,其左右分别为根节点的左、右儿子,最下层的10个是叶子节点。

深入观察一番,不难发现,假设有n个叶子节点,则一颗线段树深度不超过log2n,最少须4n的空间(否则RE)。

2)如何建树:

如果你只是想建一颗线段树以彰显自己的算法知识,那么一个一维数组 tree [105] 就足够了。

以 tree [ 1 ] 为根节点,把 tree [ 2 ] 和 tree [ 3 ] 作为其左儿子、右儿子, tree [ 4 ] 和 tree [ 5 ] 作为 tree [ 2 ] 的左儿子、右儿子, tree [ 6 ] 和 tree [ 7 ] 作为 tree [ 3 ] 的左儿子、右儿子......以此类推,你就会发现一个神奇的规律:某个父节点的子节点下标一定是该父节点下标的2倍、2倍加1。然后按照这个规律接着建树就好啦~

代码如下:

 1 struct kk{
 2     int l,r;//左右儿子的位置
 3         int lan,zhi;//懒标记,值
 4 }tree[400005];//数组大小开到n*4比较保险
 5 
 6 void build(int l,int r,int k)
 7 {
 8     tree[k].l = l;//tree是线段树数组,l和r分别是左右点位置
 9     tree[k].r = r;
10     if(l==r)//如果是同一个点,表示到达叶子节点,该输入了
11     {
12         /*
13                 tree[k].zhi = data[l];//data[r]也可以
14                 */
15         cin >> tree[k].zhi;
16         return;
17     }
18     int mid=(l+r)/2;//分成2段,二分。
19     build(l,mid,k*2);//一个位置是k*2
20     build(mid+1,r,k*2+1);//一个位置是k*2+1
21     tree[k].zhi = tree[k*2].zhi+tree[k*2+1].zhi;//父节点的值相当于2个子节点的和
22 }

 3)单点修改:

某一点数值的修改利用递归的方式来实现。简单来说,因为其区间的有序性,线段树的单点修改就好比二分,从根节点开始,判断需要修改的点在根节点左二子还是右儿子中,递归进入相应区间,再以该节点为根节点,以此类推,直到找到需要修改的点,修改数值后返回。值得注意的是,在返回后,不要忘了接着修改其父亲节点的值哦。

代码如下:

 1 void dgx(int k)//单点修改
 2 {
 3         if(tree[k].l==tree[k].r && tree[k].l==x)//当到达需修改的叶子节点
 4         {
 5                 tree[k].zhi = y;//修改
 6                 return;//返回
 7         }
 8         int mid=(tree[k].l+tree[k].r)/2;//该父节点的中间值
 9         if(x<=mid)//要改的点在左儿子
10         {
11                 dgx(k*2);//访问左儿子
12         }
13         else//否则在右儿子
14         {
15                 dgx(k*2+1);//访问右儿子
16         }
17         tree[k].zhi = tree[k*2].zhi+tree[k*2+1].zhi;//父节点区间值修改
18 }

4)区间查询(最值、和):

最值好说,建树时做预处理,查找时直接二分:

 1 int qjzz1(int k)//区间最值(区间覆盖)此处取最大值为例,最小值同理
 2 {
 3         if(tree[k].l>=x && tree[k].r<=y)//如果此节点刚好处于要求区间内
 4         {
 5                 return tree[k].mmax;//直接返回该节点下所有子树的最大值,没必要再找下去了
 6                 /*
 7                 return tree[k].mmin;
 8                 //最小值同理
 9                 */
10         }
11         int mid=(tree[k].l+tree[k].r)/2;//如果没找到,取中间值继续二分
12         int tmax=0,tmin=0;//定义临时变量存储下一轮递归后的最值
13         if(x<=mid)//如果x不大于中间值,则要找最值的范围内一定有部分或全部在左子树上
14         {
15                 tmax = max(tmax,qjzz1(k*2));//在左子树上继续递归并存储下一轮递归后的最值
16                 /*
17                 tmin = min(tmin,qjzz1(k*2));
18                 //最小值同理
19                 */
20         }
21         if(y>mid)//如果y大于中间值,则要找最值的范围内一定有部分或全部在右子树上
22         {
23                 tmax = max(tmax,qjzz1(k*2+1));//在右子树上继续递归并存储下一轮递归后的最值
24                 /*
25                 tmin = min(tmin,qjzz1(k*2+1));
26                 //最小值同理
27                 */
28         }
29         return tmax;//返回最大值
30         /*
31         return tmin;
32         //最小值同理
33         */
34 }
35 
36 int qjzz2(int k)//区间最值(区间相等)此处取最大值为例,最小值同理
37 {
38         if(tree[k].l==x && tree[k].r==y)//如果此节点左右子树刚好等于要求区间
39         {
40                 return tree[k].mmax;//直接返回该节点下所有子树的最大值,没必要再找下去了
41                 /*
42                 return tree[k].mmin;
43                 //最小值同理
44                 */
45         }
46         int mid=(tree[k].l+tree[k].r)/2;//如果没找到,取中间值继续二分
47         if(y<=mid)//如果y不大于中间值,则要找最值的范围一定全部在左子树上
48         {
49                 return qjzz1(k*2);//在左子树上继续递归并直接返回下一轮递归后的最值
50         }
51         else if(x>mid)//如果y大于中间值,则要找最值的范围一定全部在右子树上
52         {
53                 return qjzz1(k*2+1);//在右子树上继续递归并直接返回下一轮递归后的最值
54         }
55         else//否则要找最值的范围部分在左子树,部分在右子树
56         {
57                 return max(qjzz1(k*2),qjzz1(k*2+1));//在左右子树上继续递归,返回值后先取最值再返回下一轮递归后的最值
58                 /*
59                 return min(qjzz1(k*2),qjzz1(k*2+1));
60                 //最小值同理
61                 */
62         }
63         return;//返回
64 }

区间和:

定义一个全局变量,二分,在范围内直接加,不在就二分下去直到在

 1 void qjh(int k)//区间和求解
 2 {
 3     if(tree[k].l>=x && tree[k].r<=y)//如果此节点刚好处于要求解区间内
 4     {
 5         he+=tree[k].zhi;//he是全局变量,一直累加,递归结束后就是最后的加和
 6         return;//返回
 7     }
 8     if(tree[k].lan!=0)//如果懒标记不为0,说明他的子节点没有加上应该加的数,会导致误判,所以向下继承懒标记
 9         {
10         down(k);//懒标记继承
11     }
12     int mid=(tree[k].l+tree[k].r)/2;//如果没找到,取中间值继续二分
13     if(x<=mid)//如果x不大于中间值,则要求和的范围一定有部分或全部在左子树上
14     {
15         qjh(k*2);//在左子树上继续递归累加数值
16     }
17     if(y>mid)//如果y大于中间值,则要求和的范围一定有部分或全部在右子树上
18     {
19         qjh(k*2+1);//在右子树上继续递归累加数值
20     }
21     return;//返回
22 }

5)懒标记:

咱们假设每更改一个值都从头到尾改一遍,复杂度upup,如果还是多组输入输出,就容易被卡,所以就有了懒标记,就是能不改就不改,先做个标记,等要用的时候再只改一下这个点。

具体操作:

 1 void down(int k)//懒标记继承
 2 {
 3     tree[k*2].zhi+=tree[k].lan*(tree[k*2].r-tree[k*2].l+1);//左子树根据根节点计算好的懒标记数值累加相应值
 4     tree[k*2].lan+=tree[k].lan;//左子树懒标记继承(因为懒标记数值不变,计算方式不变,所以累加值不变)
 5     /*
 6     tree[k*2].mmax+=z;
 7     tree[k*2].mmin+=z;
 8     //左子树
 9     */
10     tree[k*2+1].zhi+=tree[k].lan*(tree[k*2+1].r-tree[k*2+1].l+1);//右子树根据根节点计算好的懒标记数值累加相应值
11     tree[k*2+1].lan+=tree[k].lan;//右子树懒标记继承
12     /*
13     tree[k*2+1].mmax+=z;
14     tree[k*2+1].mmin+=z;
15     //右子树
16     */
17     tree[k].lan=0;//因为左右节点懒标记已经继承,根节点懒节点再留着会引发误判,根节点懒节点清零
18     return;//直接返回
19 }

6)区间修改:

遵循能不改就不改的原则,把要修改的区间改了:

 1 void lj(int k)//区间累加(某一区间内所有数都增加同一数)
 2 {
 3     if(tree[k].l>=x && tree[k].r<=y)//如果此节点刚好处于要增加区间内
 4     {
 5         tree[k].zhi+=(tree[k].r-tree[k].l+1)*z;//先改变其本身的值
 6         tree[k].lan+=z;//再让懒标记增加
 7         /*
 8         tree[k].mmax+=z;
 9         tree[k].mmin+=z;
10         //不想写了,但题目要真让求最值也不用再用点更新函数,要哪段求哪段,反正是区间最值,不用把叶子节点的值求出来,直接加就行
11         */
12         return;//返回
13     }
14     if(tree[k].lan!=0)//如果该节点懒标记还未清零,先让其子节点继承懒标记
15     {
16         down(k);//懒标记继承
17     }
18     int mid=(tree[k].l+tree[k].r)/2;//如果没找到,取中间值继续二分
19     if(x<=mid)//如果x不大于中间值,则要增加的范围一定有部分或全部在左子树上
20     {  
21         lj(k*2);//在左子树上继续递归找范围内节点并增加
22     }
23     if(y>mid)//如果y大于中间值,则要增加的范围一定有部分或全部在右子树上
24     {
25         lj(k*2+1);//在右子树上继续递归找范围内节点并增加
26     }
27     tree[k].zhi = tree[k*2].zhi+tree[k*2+1].zhi;//此时重新计算更新后根节点的值
28         /*
29     tree[k].mmax+=z;
30     tree[k].mmin+=z;
31         //直接加
32     */
33     return;//返回
34 }

 

代码:

  1 #include<bits/stdc++.h>
  2 
  3 using namespace std;
  4 
  5 /*
  6 #difine lc k<<1
  7 #difine rc k<<1|1
  8 //可以先定义好lc和rc,分别代表k*2(lc)、k*2+1(rc),可以代替以下所有k*2和k*2+1,省劲,不用一直打
  9 */
 10 
 11 int n,m;//数据数,操作数
 12 int l,r;//左右儿子
 13 int he,t,x,y,z;//和(计数器),操作指令,x,y,z
 14 /*
 15 int data[100005];
 16 //也可以先输入数据,后给树数组赋值,但占内存
 17 */
 18 
 19 struct kk{//结构体
 20     int l,r;//左右儿子的位置
 21         int lan,zhi;//懒标记,值
 22         int mmax,mmin;//以该节点为根的树中的最大值,最小值(题目没有需要可以不写)
 23 }tree[400005];//数组大小开到n*4比较保险
 24 
 25 void build(int l,int r,int k)//建树
 26 {
 27     tree[k].l = l;//tree是线段树数组,l和r分别是左右点位置
 28     tree[k].r = r;//同上
 29     if(l==r)//如果是同一个点,表示到达叶子节点,该输入了
 30     {
 31         /*
 32                 tree[k].mmax = tree[k].mmin = tree[k].zhi = data[l];
 33                 //将data[l]换成data[r]也可以,因为data[l]==data[r] 
 34                 */
 35         cin >> tree[k].zhi;//直接输入值,比先输入数据再赋值节省内存
 36         tree[k].mmax = tree[k].mmin = tree[k].zhi;//叶子节点,最大值和最小值都为其本身
 37         return;//返回
 38     }
 39     int mid=(l+r)/2;//分成2段,二分取中间值
 40     build(l,mid,k*2);//一个位置是k*2,可在开头定义lc代替
 41     build(mid+1,r,k*2+1);//一个位置是k*2+1,rc
 42     tree[k].zhi = tree[k*2].zhi+tree[k*2+1].zhi;//父节点的值相当于2个子节点的和
 43     tree[k].mmax = max(tree[k*2].mmax,tree[k*2+1].mmax);//找其左右儿子中的最大值
 44     tree[k].mmin = min(tree[k*2].mmin,tree[k*2+1].mmin);//找其左右儿子中的最小值
 45         //题目没要求就别写最值了,打字挺累的
 46         return;//返回
 47 }
 48 
 49 void dgx(int k)//点更新
 50 {
 51         if(tree[k].l==tree[k].r && tree[k].l==x)//要更新的点一定为叶子节点
 52         {
 53         tree[k].mmax = tree[k].mmin = tree[k].zhi = y;//直接更新(叶子节点最值都为其本身)
 54                 return;//返回
 55         }
 56         int mid=(tree[k].l+tree[k].r)/2;//如果没找到,取中间值继续二分
 57         if(x<=mid)//如果x不大于中间值,则要更改的点一定在左子树上
 58         {
 59                 dgx(k*2);//在左子树上继续递归
 60         }
 61         else//否则x大于中间值,要更新的点在右子树上
 62         {
 63                 dgx(k*2+1);//在右子树上继续递归
 64         }
 65         tree[k].mmax = max(tree[k*2].mmax,tree[k*2+1].mmax);//因为有节点更新了,要重新找最大值
 66     tree[k].mmin = min(tree[k*2].mmin,tree[k*2+1].mmin);//同上,最小值
 67     //真的真的没事写写最值就行了,打字好累啊
 68     return;//返回
 69 }
 70 
 71 int qjzz1(int k)//区间最值(区间覆盖)此处取最大值为例,最小值同理
 72 {
 73         if(tree[k].l>=x && tree[k].r<=y)//如果此节点刚好处于要求区间内
 74         {
 75                 return tree[k].mmax;//直接返回该节点下所有子树的最大值,没必要再找下去了
 76                 /*
 77                 return tree[k].mmin;
 78                 //最小值同理
 79                 */
 80         }
 81         int mid=(tree[k].l+tree[k].r)/2;//如果没找到,取中间值继续二分
 82         int tmax=0,tmin=0;//定义临时变量存储下一轮递归后的最值
 83         if(x<=mid)//如果x不大于中间值,则要找最值的范围内一定有部分或全部在左子树上
 84         {
 85                 tmax = max(tmax,qjzz1(k*2));//在左子树上继续递归并存储下一轮递归后的最值
 86                 /*
 87                 tmin = min(tmin,qjzz1(k*2));
 88                 //最小值同理
 89                 */
 90         }
 91         if(y>mid)//如果y大于中间值,则要找最值的范围内一定有部分或全部在右子树上
 92         {
 93                 tmax = max(tmax,qjzz1(k*2+1));//在右子树上继续递归并存储下一轮递归后的最值
 94                 /*
 95                 tmin = min(tmin,qjzz1(k*2+1));
 96                 //最小值同理
 97                 */
 98         }
 99         return tmax;//返回最大值
100         /*
101         return tmin;
102         //最小值同理
103         */
104 }
105 
106 int qjzz2(int k)//区间最值(区间相等)此处取最大值为例,最小值同理
107 {
108         if(tree[k].l==x && tree[k].r==y)//如果此节点左右子树刚好等于要求区间
109         {
110                 return tree[k].mmax;//直接返回该节点下所有子树的最大值,没必要再找下去了
111                 /*
112                 return tree[k].mmin;
113                 //最小值同理
114                 */
115         }
116         int mid=(tree[k].l+tree[k].r)/2;//如果没找到,取中间值继续二分
117         if(y<=mid)//如果y不大于中间值,则要找最值的范围一定全部在左子树上
118         {
119                 return qjzz1(k*2);//在左子树上继续递归并直接返回下一轮递归后的最值
120         }
121         else if(x>mid)//如果y大于中间值,则要找最值的范围一定全部在右子树上
122         {
123                 return qjzz1(k*2+1);//在右子树上继续递归并直接返回下一轮递归后的最值
124         }
125         else//否则要找最值的范围部分在左子树,部分在右子树
126         {
127                 return max(qjzz1(k*2),qjzz1(k*2+1));//在左右子树上继续递归,返回值后先取最值再返回下一轮递归后的最值
128                 /*
129                 return min(qjzz1(k*2),qjzz1(k*2+1));
130                 //最小值同理
131                 */
132         }
133         return;//返回
134 }
135 
136 void down(int k)//懒标记继承
137 {
138     tree[k*2].zhi+=tree[k].lan*(tree[k*2].r-tree[k*2].l+1);//左子树根据根节点计算好的懒标记数值累加相应值
139     tree[k*2].lan+=tree[k].lan;//左子树懒标记继承(因为懒标记数值不变,计算方式不变,所以累加值不变)
140     /*
141     tree[k*2].mmax+=z;
142     tree[k*2].mmin+=z;
143     //左子树
144     */
145     tree[k*2+1].zhi+=tree[k].lan*(tree[k*2+1].r-tree[k*2+1].l+1);//右子树根据根节点计算好的懒标记数值累加相应值
146     tree[k*2+1].lan+=tree[k].lan;//右子树懒标记继承
147     /*
148     tree[k*2+1].mmax+=z;
149     tree[k*2+1].mmin+=z;
150     //右子树
151     */
152     tree[k].lan=0;//因为左右节点懒标记已经继承,根节点懒节点再留着会引发误判,根节点懒节点清零
153     return;//直接返回
154 }
155 
156 void qjh(int k)//区间和求解
157 {
158     if(tree[k].l>=x && tree[k].r<=y)//如果此节点刚好处于要求解区间内
159     {
160         he+=tree[k].zhi;//he是全局变量,一直累加,递归结束后就是最后的加和
161         return;//返回
162     }
163     if(tree[k].lan!=0)//如果懒标记不为0,说明他的子节点没有加上应该加的数,会导致误判,所以向下继承懒标记
164         {
165         down(k);//懒标记继承
166     }
167     int mid=(tree[k].l+tree[k].r)/2;//如果没找到,取中间值继续二分
168     if(x<=mid)//如果x不大于中间值,则要求和的范围一定有部分或全部在左子树上
169     {
170         qjh(k*2);//在左子树上继续递归累加数值
171     }
172     if(y>mid)//如果y大于中间值,则要求和的范围一定有部分或全部在右子树上
173     {
174         qjh(k*2+1);//在右子树上继续递归累加数值
175     }
176     return;//返回
177 }
178 
179 void lj(int k)//区间累加(某一区间内所有数都增加同一数)
180 {
181     if(tree[k].l>=x && tree[k].r<=y)//如果此节点刚好处于要增加区间内
182     {
183         tree[k].zhi+=(tree[k].r-tree[k].l+1)*z;//先改变其本身的值
184         tree[k].lan+=z;//再让懒标记增加
185         /*
186         tree[k].mmax+=z;
187         tree[k].mmin+=z;
188         //不想写了,但题目要真让求最值也不用再用点更新函数,要哪段求哪段,反正是区间最值,不用把叶子节点的值求出来,直接加就行
189         */
190         return;//返回
191     }
192     if(tree[k].lan!=0)//如果该节点懒标记还未清零,先让其子节点继承懒标记
193     {
194         down(k);//懒标记继承
195     }
196     int mid=(tree[k].l+tree[k].r)/2;//如果没找到,取中间值继续二分
197     if(x<=mid)//如果x不大于中间值,则要增加的范围一定有部分或全部在左子树上
198     {  
199         lj(k*2);//在左子树上继续递归找范围内节点并增加
200     }
201     if(y>mid)//如果y大于中间值,则要增加的范围一定有部分或全部在右子树上
202     {
203         lj(k*2+1);//在右子树上继续递归找范围内节点并增加
204     }
205     tree[k].zhi = tree[k*2].zhi+tree[k*2+1].zhi;//此时重新计算更新后根节点的值
206         /*
207     tree[k].mmax+=z;
208     tree[k].mmin+=z;
209         //直接加
210     */
211     return;//返回
212 }
213 
214 int main()
215 {
216     cin >> n >> m;//输入
217     build(1,n,1);//建树
218     /*
219         for(int i=1; i<=n; i++)//循环输入
220         {
221                 cin >> data[i];
222                 //可以,但没先输入数据再赋值节省内存
223         }
224         //
225         */
226     while(m--)//操作m次
227     {
228         cin >> t;//输入
229         if(t==1)//若t==1,将第x个点的值更改为y
230         {
231                 cin >> x >> y;//输入
232                 dgx(1);//点更新
233                 }
234                 else if(t==2)//若t==2,找第x个点到第y个点的最值(区间覆盖法)
235         {
236             cin >> x >> y;//输入
237             cout << qjzz1(1) << endl;//递归并输出
238         }
239         else if(t==3)//若t==3,找第x个点到第y个点的最值(区间相等法)
240         {
241             cin >> x >> y;//输入
242             cout << qjzz2(1) << endl;//递归并输出
243         }
244         else if(t==4)//若t==4,将第x个点到第y个点之间的每个点的值加z
245         {
246             cin >> x >> y >> z;//输入
247             lj(1);//递归增加
248         }
249                 else if(t==5)//若t==5,将第x个点到第y个点之间的每个点的值累加
250         {
251             cin >> x >> y;//输入
252             he = 0;//计数器清零
253             qjh(1);//递归求和
254             cout << he << endl;//输出
255         }
256     }
257     return 0;
258 }

 

练习:

1.P3372

和上面讲的差不多,自己默写一下就能过(注意细节)

  1 #include<bits/stdc++.h>
  2 
  3 using namespace std;
  4 
  5 int n,m;
  6 
  7 struct kk{
  8     int l,r;
  9     long long val,lan;
 10 }tree[400005];
 11 
 12 int t,x,y,z;
 13 long long he;
 14 
 15 void build(int l,int r,int k)
 16 {
 17     tree[k].l = l;
 18     tree[k].r = r;
 19     if(l==r)
 20     {
 21         cin >> tree[k].val;
 22         return;
 23     }
 24     int mid=(l+r)/2;
 25     build(l,mid,k*2);
 26     build(mid+1,r,k*2+1);
 27     tree[k].val = tree[k*2].val+tree[k*2+1].val;
 28     
 29 }
 30 
 31 void down(int k)
 32 {
 33     tree[k*2].val+=tree[k].lan*(tree[k*2].r-tree[k*2].l+1);
 34     tree[k*2].lan+=tree[k].lan;
 35     tree[k*2+1].val+=tree[k].lan*(tree[k*2+1].r-tree[k*2+1].l+1);
 36     tree[k*2+1].lan+=tree[k].lan;
 37     tree[k].lan = 0;
 38     return;
 39 }
 40 
 41 void lj(int k)
 42 {
 43     if((tree[k].l>=x) && (tree[k].r<=y))
 44     {
 45         tree[k].val+=z*(tree[k].r-tree[k].l+1);
 46         tree[k].lan+=z;
 47         return;
 48     }
 49     if(tree[k].lan)
 50     {
 51         down(k);
 52     }
 53     int mid=(tree[k].l+tree[k].r)/2;
 54     if(x<=mid)
 55     {
 56         lj(k*2);
 57     }
 58     if(y>mid)
 59     {
 60         lj(k*2+1);
 61     }
 62     tree[k].val = tree[k*2].val+tree[k*2+1].val;
 63     return;
 64 }
 65 
 66 void qjh(int k)
 67 {
 68     if((tree[k].l>=x) && (tree[k].r<=y))
 69     {
 70         he+=tree[k].val;
 71         return;
 72     }
 73     if(tree[k].lan)
 74     {
 75         down(k);
 76     }
 77     int mid=(tree[k].l+tree[k].r)/2;
 78     if(x<=mid)
 79     {
 80         qjh(k*2);
 81     }
 82     if(y>mid)
 83     {
 84         qjh(k*2+1);
 85     }
 86     return;
 87 }
 88 
 89 int main()
 90 {
 91     cin >> n >> m;
 92     build(1,n,1);
 93     while(m--)
 94     {
 95         cin >> t >> x >> y;
 96         if(t==1)
 97         {
 98             cin >> z;
 99             lj(1);
100         }
101         else
102         {
103             he = 0;
104             qjh(1);
105             cout << he << endl;
106         }
107     }
108     return 0;
109 }

 

2.P3372

区间乘法可能不太好写,也是懒标记,但推式子比较麻烦;

取模记得算一次模一次,有乘法还要注意乘法的模和单纯加法不太一样

  1 #include<bits/stdc++.h>
  2 
  3 using namespace std;
  4 
  5 long long n,m,mod;
  6 
  7 struct kk{
  8     long long l,r;
  9     long long cheng;
 10     long long val,lan;
 11 }tree[4000005];
 12 
 13 int t,x,y,z;
 14 long long he;
 15 
 16 void build(long long l,long long r,int k)
 17 {
 18     tree[k].l = l;
 19     tree[k].r = r;
 20     tree[k].cheng = 1;
 21     if(l==r)
 22     {
 23         cin >> tree[k].val;
 24         tree[k].val = tree[k].val%mod;
 25         return;
 26     }
 27     long long mid=(l+r)/2;
 28     build(l,mid,k*2);
 29     build(mid+1,r,k*2+1);
 30     tree[k].val = (tree[k*2].val+tree[k*2+1].val)%mod;
 31     
 32 }
 33 
 34 void down(long long k)
 35 {
 36     tree[k*2].val = (tree[k].cheng*tree[k*2].val+((tree[k*2].r-tree[k*2].l+1)*tree[k].lan)%mod)%mod;
 37     tree[k*2].lan = (tree[k*2].lan*tree[k].cheng+tree[k].lan)%mod;
 38     tree[k*2].cheng = (tree[k*2].cheng*tree[k].cheng)%mod;
 39     
 40     tree[k*2+1].val = (tree[k].cheng*tree[k*2+1].val+((tree[k*2+1].r-tree[k*2+1].l+1)*tree[k].lan)%mod)%mod;
 41     tree[k*2+1].lan = (tree[k*2+1].lan*tree[k].cheng+tree[k].lan)%mod;
 42     tree[k*2+1].cheng = (tree[k*2+1].cheng*tree[k].cheng)%mod;
 43     
 44 //    cout << "---" << endl << tree[k*2].val << " " << tree[k*2].lan << " " << tree[k*2].cheng << " " << tree[k*2+1].val << " " << tree[k*2+1].lan << " " << tree[k*2].cheng << endl;
 45     tree[k].lan = 0;
 46     tree[k].cheng = 1;
 47     return;
 48 }
 49 
 50 void lc(long long k)
 51 {
 52     if((tree[k].l>=x) && (tree[k].r<=y))
 53     {
 54         tree[k].lan = (tree[k].lan*z)%mod;
 55         tree[k].val= (z*tree[k].val)%mod;
 56         tree[k].cheng = (tree[k].cheng*z)%mod;
 57         return;
 58     }
 59 //    if(tree[k].lan)
 60 //    {
 61         down(k);
 62 //    }
 63     long long mid=(tree[k].l+tree[k].r)/2;
 64     if(x<=mid)
 65     {
 66         lc(k*2);
 67     }
 68     if(y>mid)
 69     {
 70         lc(k*2+1);
 71     }
 72     tree[k].val = (tree[k*2].val+tree[k*2+1].val)%mod;
 73 }
 74 
 75 void lj(long long k)
 76 {
 77     if((tree[k].l>=x) && (tree[k].r<=y))
 78     {
 79         tree[k].val = (tree[k].val+z*(tree[k].r-tree[k].l+1))%mod;
 80         tree[k].lan = (z+tree[k].lan)%mod;
 81         return;
 82     }
 83 //    if(tree[k].lan)
 84 //    {
 85         down(k);
 86 //    }
 87     long long mid=(tree[k].l+tree[k].r)/2;
 88     if(x<=mid)
 89     {
 90         lj(k*2);
 91     }
 92     if(y>mid)
 93     {
 94         lj(k*2+1);
 95     }
 96     tree[k].val = (tree[k*2].val+tree[k*2+1].val)%mod;
 97     return;
 98 }
 99 
100 void qjh(long long k)
101 {
102     if((tree[k].l>=x) && (tree[k].r<=y))
103     {
104         he = (he+tree[k].val)%mod;
105         return;
106     }
107 //    if(tree[k].lan)
108 //    {
109         down(k);
110 //    }
111     long long mid=(tree[k].l+tree[k].r)/2;
112     if(x<=mid)
113     {
114         qjh(k*2);
115     }
116     if(y>mid)
117     {
118         qjh(k*2+1);
119     }
120     return;
121 }
122 
123 int main()
124 {
125     cin >> n >> m >> mod;
126     build(1,n,1);
127     while(m--)
128     {
129         cin >> t >> x >> y;
130         if(t==1)
131         {
132             cin >> z;
133             lc(1);
134         }
135         else if(t==2)
136         {
137             cin >> z;
138             lj(1);
139         }
140         else
141         {
142             he = 0;
143             qjh(1);
144             cout << he << endl;
145         }
146     }
147     return 0;
148 }

3.P4588

假设x为根,每次操作维护区间乘,1时加点,2时改点值为1

 1 #include<bits/stdc++.h>
 2 
 3 using namespace std;
 4 
 5 int t,q;
 6 long long m;
 7 long long data[500005];
 8 
 9 void build(int l,int r,int k)
10 {
11     if(l==r)
12     {
13         data[k] = 1;
14         return;
15     }
16     int mid=(l+r)/2;
17     build(l,mid,k*2);
18     build(mid+1,r,k*2+1);
19     data[k] = (data[k*2]*data[k*2+1])%m;
20 }
21 
22 void change(int k,int l,int r,int x,int y,int zhi)
23 {
24     if((l>=x) && (r<=y))
25     {
26         data[k] = zhi;
27         return;
28     }
29     int mid=(l+r)/2;
30     if(x<=mid) change(k*2,l,mid,x,y,zhi);
31     if(y>mid) change(k*2+1,mid+1,r,x,y,zhi);
32     data[k] = (data[k*2]*data[k*2+1])%m;
33     return;
34 }
35 
36 int main()
37 {
38     cin >> t;
39     while(t--)
40     {
41         cin >> q >> m;
42         build(1,q,1);
43         for(int i=1; i<=q; i++)
44         {
45             int a;
46             cin >> a;
47             if(a==1)
48             {
49                 cin >> a;
50                 change(1,1,q,i,i,a);
51                 data[1]%=m;
52             }
53             else
54             {
55                 cin >> a;
56                 change(1,1,q,a,a,1);
57             }
58             cout << data[1]%m << endl;
59         }
60     }
61     return 0;
62 }

4.P5142

方差计算公式推导:

 可以看出,求方差只要区间算数平均数方和区间平方和,而算数平均数只要区间和就可以,那么就维护区间和、区间平方和就好

  1 #include<bits/stdc++.h>
  2 
  3 using namespace std;
  4 
  5 const int mod=1000000007;
  6 
  7 long long n,m;
  8 //long long b[100005];
  9 
 10 struct kk{
 11     long long val;
 12     long long pow;
 13 }tree[500005];
 14 
 15 //void build(int l,int r,int k)
 16 //{
 17 //    if(l==r)
 18 //    {
 19 //        long long x;
 20 //        cin >> x;
 21 //        tree[k].val = x;
 22 //        tree[k].pow = (x*x)%mod;
 23 //        return;
 24 //    }
 25 //    int mid=(l+r)/2;
 26 //    build(l,mid,k*2);
 27 //    build(mid+1,r,k*2+1);
 28 //    tree[k].val = (tree[k*2].val+tree[k*2+1].val)%mod;
 29 //    tree[k].pow = (tree[k*2].pow+tree[k*2+1].pow)%mod;
 30 //}
 31 
 32 void build(long long l,long long r,long long k)
 33 {
 34     if(l==r)
 35     {
 36         long long x;
 37         cin >> x;
 38         tree[k].val=x;
 39         tree[k].pow=(x*x)%mod;
 40         return;
 41     }
 42     int mid=(l+r)/2;
 43     build(l,mid,k*2);
 44     build(mid+1,r,k*2+1);
 45     tree[k].val = (tree[k*2].val+tree[k*2+1].val)%mod;
 46     tree[k].pow = (tree[k*2].pow+tree[k*2+1].pow)%mod;
 47     return;
 48 }
 49 
 50 void change(int l,int r,int x,int y,long long zhi,int k)
 51 {
 52 //    if((l==x) && (r==y))
 53     if(l==r)
 54     {
 55         tree[k].val = zhi%mod;
 56         tree[k].pow = (zhi*zhi)%mod;
 57 //        cout << l << endl;
 58         return;
 59     }
 60     int mid=(l+r)/2;
 61     if(x<=mid)
 62     {
 63         change(l,mid,x,y,zhi,k*2);
 64     }
 65 //    if(y>mid)
 66     else
 67     {
 68         change(mid+1,r,x,y,zhi,k*2+1);
 69     }
 70     tree[k].val = (tree[k*2].val+tree[k*2+1].val)%mod;
 71     tree[k].pow = (tree[k*2].pow+tree[k*2+1].pow)%mod;
 72 //    cout << "___ " << tree[k].val << " " << tree[k].pow << endl;
 73     return;
 74 }
 75 
 76 long long query(int k,int l,int r,int x,int y,bool f)
 77 {
 78     if((l>=x) && (r<=y))
 79     {
 80         return f? tree[k].val:tree[k].pow;
 81     }
 82     int mid=(l+r)/2;
 83     long long res=0;
 84     if(x<=mid)
 85     {
 86         res = (res+query(k*2,l,mid,x,y,f))%mod;
 87     }
 88     if(y>mid)
 89     {
 90         res = (res+query(k*2+1,mid+1,r,x,y,f))%mod;
 91     }
 92     return res;
 93 }
 94 
 95 long long qpow(long long a,long long b)
 96 {
 97     long long res=1;
 98     while(b)
 99     {
100         if(b&1) res = (res*a)%mod;
101         b>>=1;
102         a = (a*a)%mod;
103     }
104     return res;
105 }
106 
107 int main()
108 {
109     cin >> n >> m;
110     build(1,n,1);
111     while(m--)
112     {
113         long long t,x,y;
114         cin >> t >> x >> y;
115         if(t==1)
116         {
117             change(1,n,x,x,y,1);
118         }
119         else
120         {
121 //            if(x==y) cout << "0" << endl;
122 //            else
123 //            {
124                 long long k=qpow(y-x+1,mod-2),s=query(1,1,n,x,y,true),sp=query(1,1,n,x,y,false);
125 //                cout << "--- " << k << " " << s << " " << sp << endl;
126                 cout << ((sp*k)%mod-(((s*k)%mod)*((s*k)%mod))%mod+mod)%mod << endl;
127 //            }
128         }
129 //        for(int i=1; i<=n; i++) cout << b[i] << " ";
130 //        cout << endl;
131     }
132     return 0;
133 }

 

没啦~~~