线段树
前言:
继树状数组,一年后,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 }
没啦~~~