线段树入门2 洛谷【P3373】【模板】线段树2

  在线段树入门 洛谷【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

posted @ 2018-06-07 11:24  rp++  阅读(485)  评论(0编辑  收藏  举报