在线段树入门 洛谷【P3372】【模板】线段树1中我们简单地了解了线段树的 pushup 和 pushdown 操作(对线段树还不了解的同学可以参考一下QwQ)。但那是只有一个懒标记的线段树,如果有多个懒标记,我们该如何处理好 pushup 和 pushdown 呢?请看下面这道例题:
洛谷【P3373】【模板】线段树2
【题目大意】
第一行输入三个整数n,m,p
第二行包含n个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。
接下来m行每行包含3或4个整数,表示一个操作,具体如下:
操作1: 格式:1 x y k 含义:将区间[x,y]内每个数乘上k
操作2: 格式:2 x y k 含义:将区间[x,y]内每个数加上k
操作3: 格式:3 x y 含义:输出区间[x,y]内每个数的和对p取模所得的结果
样例输入
5 5 38
1 5 4 2 3
2 1 4 1
3 2 5
1 2 4 2
2 3 5 5
3 1 4
样例输出
17
2
【解题思路】
1、建树
仍然采用动态开点的方法,不了解的同学请参考线段树入门 洛谷【P3372】【模板】线段树1。
1 #define N 200100 //两倍空间 2 struct node { 3 int l, r, lc, rc; 4 long long v, add, mul; //数据比较大,记得定long long 5 node() { 6 add = 0, mul = 1; //构造函数,初始化懒标记 7 } 8 } tr[N]; 9 void pushup(int lc, int rc, int rt) { //上浮 10 tr[rt].v = (tr[lc].v + tr[rc].v) % p; 11 } 12 void build(int l, int r, int & rt) { 13 rt = ++tot; 14 tr[rt].l = l, tr[rt].r = r; 15 if (l == r) { //找到叶子结点 16 scanf("%lld", &tr[rt].v); 17 return; 18 } 19 int mid = (l + r) >> 1; 20 build(l, mid, tr[rt].lc); //建左子树 21 build(mid + 1, r, tr[rt].rc); //建右子树 22 pushup(tr[rt].lc, tr[rt].rc, rt); //上浮 23 }
2、修改
pushdown 操作
1 void pushdown(int lc, int rc, int rt) { //下沉,此题关键 2 tr[lc].v = (tr[lc].v * tr[rt].mul + (tr[lc].r - tr[lc].l + 1) * tr[rt].add) % p; //先乘后加 3 tr[rc].v = (tr[rc].v * tr[rt].mul + (tr[rc].r - tr[rc].l + 1) * tr[rt].add) % p; 4 tr[lc].mul = (tr[lc].mul * tr[rt].mul) % p; 5 tr[rc].mul = (tr[rc].mul * tr[rt].mul) % p; 6 tr[lc].add = (tr[lc].add * tr[rt].mul + tr[rt].add) % p; //一定是先乘后加! 7 tr[rc].add = (tr[rc].add * tr[rt].mul + tr[rt].add) % p; 8 tr[rt].mul = 1; 9 tr[rt].add = 0; 10 }
为什么是先乘后加呢?因为。。。我等会再告诉你233(接着往下看吧QwQ)。
加法
1 void add(int l, int r, int rt) { //跟线段树1基本没有区别 2 if (x <= l && y >= r) { 3 tr[rt].add = (tr[rt].add + k) % p; 4 tr[rt].v = (tr[rt].v + (r - l + 1) * k) % p; 5 return; 6 } 7 pushdown(tr[rt].lc, tr[rt].rc, rt); 8 int mid = (l + r) >> 1; 9 if (x <= mid) 10 add(l, mid, tr[rt].lc); 11 if (y > mid) 12 add(mid + 1, r, tr[rt].rc); 13 pushup(tr[rt].lc, tr[rt].rc, rt); 14 }
乘法
1 void mul(int l, int r, int rt) { 2 if (x <= l && y >= r) { 3 tr[rt].mul = tr[rt].mul * k % p; 4 tr[rt].add = tr[rt].add * k % p; //add也要乘k(这就是pushdown先乘后加的原因)! 5 tr[rt].v = tr[rt].v * k % p; 6 return; 7 } 8 pushdown(tr[rt].lc, tr[rt].rc, rt); 9 int mid = (l + r) >> 1; 10 if (x <= mid) 11 mul(l, mid, tr[rt].lc); 12 if (y > mid) 13 mul(mid + 1, r, tr[rt].rc); 14 pushup(tr[rt].lc, tr[rt].rc, rt); 15 }
看到这里想必大家都明白为什么 pushdown 中要先乘后加了吧。当对某区间执行加法操作时,由于加法优先级低,不会对乘法操作产生影响,故直接相加即可;当对某区间执行乘法操作时,由于乘法优先级高,会对之前的加法操作产生影响,故需要在相乘时不仅对 mul 和 v 相乘,也需要对 add 相乘。由于上述原因,故需要先算乘法再算加法^_^。
3、查询
1 long long getans(int l, int r, int rt) { //同线段树1 2 if (x <= l && y >= r) 3 return tr[rt].v; 4 pushdown(tr[rt].lc, tr[rt].rc, rt); 5 long long ans = 0; 6 int mid = (l + r) >> 1; 7 if (x <= mid) 8 ans += getans(l, mid, tr[rt].lc); 9 if (y > mid) 10 ans += getans(mid + 1, r, tr[rt].rc); 11 return ans % p; 12 }
与线段树1基本没有差别。
----------------------------------------------------------------华丽的分割线----------------------------------------------------------------------
下面上代码^_^
1 #include <cstdio> 2 #define N 200100 //两倍空间 3 using namespace std; 4 int n, m, root, tot, p, q, x, y; 5 long long k; 6 struct node { 7 int l, r, lc, rc; 8 long long v, add, mul; //数据比较大,记得定long long 9 node() { 10 add = 0, mul = 1; //构造函数,初始化懒标记 11 } 12 } tr[N]; 13 void pushup(int lc, int rc, int rt) { //上浮 14 tr[rt].v = (tr[lc].v + tr[rc].v) % p; 15 } 16 void pushdown(int lc, int rc, int rt) { //下沉,此题关键 17 tr[lc].v = (tr[lc].v * tr[rt].mul + (tr[lc].r - tr[lc].l + 1) * tr[rt].add) % p; //先乘后加 18 tr[rc].v = (tr[rc].v * tr[rt].mul + (tr[rc].r - tr[rc].l + 1) * tr[rt].add) % p; 19 tr[lc].mul = (tr[lc].mul * tr[rt].mul) % p; 20 tr[rc].mul = (tr[rc].mul * tr[rt].mul) % p; 21 tr[lc].add = (tr[lc].add * tr[rt].mul + tr[rt].add) % p; //一定是先乘后加! 22 tr[rc].add = (tr[rc].add * tr[rt].mul + tr[rt].add) % p; 23 tr[rt].mul = 1; 24 tr[rt].add = 0; 25 } 26 void build(int l, int r, int & rt) { 27 rt = ++tot; 28 tr[rt].l = l, tr[rt].r = r; 29 if (l == r) { //找到叶子结点 30 scanf("%lld", &tr[rt].v); 31 return; 32 } 33 int mid = (l + r) >> 1; 34 build(l, mid, tr[rt].lc); //建左子树 35 build(mid + 1, r, tr[rt].rc); //建右子树 36 pushup(tr[rt].lc, tr[rt].rc, rt); //上浮 37 } 38 void add(int l, int r, int rt) { //跟线段树1基本没有区别 39 if (x <= l && y >= r) { 40 tr[rt].add = (tr[rt].add + k) % p; 41 tr[rt].v = (tr[rt].v + (r - l + 1) * k) % p; 42 return; 43 } 44 pushdown(tr[rt].lc, tr[rt].rc, rt); 45 int mid = (l + r) >> 1; 46 if (x <= mid) 47 add(l, mid, tr[rt].lc); 48 if (y > mid) 49 add(mid + 1, r, tr[rt].rc); 50 pushup(tr[rt].lc, tr[rt].rc, rt); 51 } 52 void mul(int l, int r, int rt) { 53 if (x <= l && y >= r) { 54 tr[rt].mul = tr[rt].mul * k % p; 55 tr[rt].add = tr[rt].add * k % p; //add也要乘k(这就是pushdown先乘后加的原因)! 56 tr[rt].v = tr[rt].v * k % p; 57 return; 58 } 59 pushdown(tr[rt].lc, tr[rt].rc, rt); 60 int mid = (l + r) >> 1; 61 if (x <= mid) 62 mul(l, mid, tr[rt].lc); 63 if (y > mid) 64 mul(mid + 1, r, tr[rt].rc); 65 pushup(tr[rt].lc, tr[rt].rc, rt); 66 } 67 long long getans(int l, int r, int rt) { //同线段树1 68 if (x <= l && y >= r) 69 return tr[rt].v; 70 pushdown(tr[rt].lc, tr[rt].rc, rt); 71 long long ans = 0; 72 int mid = (l + r) >> 1; 73 if (x <= mid) 74 ans += getans(l, mid, tr[rt].lc); 75 if (y > mid) 76 ans += getans(mid + 1, r, tr[rt].rc); 77 return ans % p; 78 } 79 int main() { 80 scanf("%d %d %d", &n, &m, &p); 81 build(1, n, root); 82 while (m--) { 83 scanf("%d %d %d", &q, &x, &y); 84 switch(q) { 85 case 1: 86 scanf("%lld", &k); 87 mul(1, n, 1); 88 break; 89 case 2: 90 scanf("%lld", &k); 91 add(1, n, 1); 92 break; 93 case 3: 94 printf("%lld\n", getans(1, n, 1)); 95 } 96 } 97 return 0; 98 }
就讲到这吧,写题解不易,希望大家多多资瓷下,对我来说也是一种动力。另外,如果有什么不懂的地方,欢迎在下方评论留言,我会尽量解答QwQ