• [ZJOI2019]线段树


    基于观察可以发现:每次的修改操作相当于将之前的 (2 ^ {t - 1}) 棵线段树做或不做当前修改后形成的 (2 ^ t) 棵线段树。

    换句话说,(t) 次修改后的每颗线段树都是在每一时刻选择是否修改所得到的 (2 ^ t) 种选择。

    这样我们就了解了操作一的本质,也提示着每一轮本质上只是选或不选择操作,这类问题通常可以使用 (dp) 解决。

    继续观察可以发现,每次修改会被影响 ( m tag) 的节点的种类是很少的。

    大致有这几种:被修改区间完全包含的最上层区间,被修改区间半包含的区间,与修改区间交集为空且父亲与修改区间半包含的区间。

    这提示着我们要将线段树上的节点进行分类,基于观察可以将线段树上的节点大致分为五类。

    1. 被修改区间半包含的节点
    2. 被修改区间完全包含的最上层节点
    3. 被修改区间完全包含且在最上层节点之下的节点
    4. 与修改区间无交集但父亲与修改区间半包含的区间
    5. 与修改区间无交集且父亲区间与修改区间也无交集的区间

    既然如此,我们可以考虑基于上面的分类使用最开始 (dp) 的想法在线段树的节点上进行 (dp)

    最简单的想法是令 (f_{i, j}) 表示当前操作一进行了 (i) 次,节点 (j) 在多少棵线段树上存在标记。

    1. 对于第一类节点,如果它本身有 ( m tag) 那么在经过时必然会被下方。因此对于此类节点只要操作就必然不存在懒标记,则有转移:

    [f_{i, j} = f_{i - 1, j} ]

    1. 对于第二类节点,只要操作就必然会在其上面打 ( m tag),因此有转移:

    [f_{i, j} = f_{i - 1, j} + 2 ^ {i - 1} ]

    1. 对于第三类节点,不论修改还是不修改都不会影响这个点的 ( m tag) 都会保持原样,于是有转移:

    [f_{i, j} = 2 imes f_{i - 1, j} ]

    1. 对于第四类节点,如果修改前没有懒标记若要获得懒标记,其到根的路径上必然要有懒标记,但是此时我们不知道其到根路径上的状态,因此还需要引入 (g_{i, j}) 表示第 (i) 次操作一后 (j) 节点到根路径上存在懒标记的线段树个数,于是有转移:

    [f_{i, j} = f_{i - 1, j} + g_{i - 1, j} ]

    1. 对于第五类节点,不论操作或不操作都不会影响懒标记的情况,于是有转移:

    [f_{i, j} = 2 imes f_{i - 1, j} ]

    对于 (g) 的转移同理,不再赘述。

    那么每次修改的 (dp) 值会达到 (n) 个,复杂度 (mathcal{O(nm)}) 还不能通过本题,需要近一步优化。

    观察转移可以发现:

    1. 对于第一类节点,直接继承之前的 (dp) 值即可,不需考虑。
    2. 对于第二类节点,在线段树修改时只会存在 (log n) 个,暴力修改即可。
    3. 对于第三类节点,相当于是将线段树某个节点下面的所有节点 ( imes 2) 直接懒标记维护即可。
    4. 对于第四类节点,在线段树修改时只会存在 (log n) 个,暴力修改即可。
    5. 对于第五类节点,同理于第三类节点,使用懒标记对子树整体 ( imes 2)

    (g) 的维护类似,同时需要多维护每个节点整个子树内 (Ans = sum f_{i, j}),复杂度 (mathcal{O(m log n)})

    注意一些代码细节:数组越界和下传懒标记/( m pushup) 的位置问题。

    #include <bits/stdc++.h>
    using namespace std;
    #define ls (p << 1)
    #define rs (p << 1 | 1)
    #define mid (l + r >> 1)
    #define rep(i, l, r) for (int i = l; i <= r; ++i)
    const int N = 1e5 + 5;
    const int Mod = 998244353;
    struct tree { int f, g, sum, tagf1, tagf2, tagg1, tagg2;} t[N << 2];
    int n, m, l, r, opt, tmp, P[N];
    int read() {
        char c; int x = 0, f = 1;
        c = getchar();
        while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
        while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x * f;
    }
    int Inc(int a, int b) { return (a += b) >= Mod ? a - Mod : a;}
    int Mul(int a, int b) { return 1ll * a * b % Mod;}
    void up(int p, int l, int r) {
        if(l != r) t[p].sum = Inc(t[p].f, Inc(t[ls].sum, t[rs].sum)); // 这里不这样写会越界
        else t[p].sum = t[p].f;
    }
    void lazy(int p, int tagf1, int tagf2, int tagg1, int tagg2) { // 开始这里返回值又写成了 int ...
        t[p].f = Inc(Mul(t[p].f, tagf1), tagf2);
        t[p].sum = Inc(Mul(t[p].sum, tagf1), tagf2);
        t[p].g = Inc(Mul(t[p].g, tagg1), tagg2);
        t[p].tagf1 = Mul(t[p].tagf1, tagf1);
        t[p].tagf2 = Inc(Mul(t[p].tagf2, tagf1), tagf2);
        t[p].tagg1 = Mul(t[p].tagg1, tagg1);
        t[p].tagg2 = Inc(Mul(t[p].tagg2, tagg1), tagg2);
    }
    void down(int p) {
        lazy(ls, t[p].tagf1, t[p].tagf2, t[p].tagg1, t[p].tagg2);
        lazy(rs, t[p].tagf1, t[p].tagf2, t[p].tagg1, t[p].tagg2);
        t[p].tagf1 = t[p].tagg1 = 1, t[p].tagf2 = t[p].tagg2 = 0;
    }
    void build(int p, int l, int r) {
        t[p].tagf1 = t[p].tagg1 = 1;
        if(l == r) return;
        build(ls, l, mid), build(rs, mid + 1, r);
    }
    void update(int p, int l, int r, int x, int y) {
        if(l != r) down(p); // 因为下面需要调用 ls, rs 于是先 down
        if(l >= x && r <= y) {
            t[p].f = Inc(t[p].f, P[tmp - 1]);
            t[p].g = Inc(t[p].g, P[tmp - 1]);
            if(l != r) lazy(ls, 2, 0, 1, P[tmp - 1]), lazy(rs, 2, 0, 1, P[tmp - 1]);
            up(p, l, r); return;
        }
        if(mid >= y) {
            update(ls, l, mid, x, y);
            t[rs].f = Inc(t[rs].f, t[rs].g);
            t[rs].g = Mul(t[rs].g, 2);
            if(mid + 1 != r) down(rs), lazy((rs << 1), 2, 0, 2, 0), lazy((rs << 1) + 1, 2, 0, 2, 0);
            up(rs, mid + 1, r); // 注意这里是 up rs
        }
        else if(mid < x) {
            update(rs, mid + 1, r, x, y);
            t[ls].f = Inc(t[ls].f, t[ls].g);
            t[ls].g = Mul(t[ls].g, 2);
            if(l != mid) down(ls), lazy((ls << 1), 2, 0, 2, 0), lazy((ls << 1) + 1, 2, 0, 2, 0);
            up(ls, l, mid); // 注意这里是 up ls
        }
        else update(ls, l, mid, x, y), update(rs, mid + 1, r, x, y);
        up(p, l, r);
    }
    int main() {
        n = read(), m = read();
        build(1, 1, n);
        P[0] = 1; rep(i, 1, m) P[i] = Mul(P[i - 1], 2);
        rep(i, 1, m) {
            opt = read();
            if(opt == 1) ++tmp, l = read(), r = read(), update(1, 1, n, l, r);
            else printf("%d
    ", t[1].sum);
        }
        return 0;
    }
    

    可以发现,本题的做法是基于线段树上修改时懒标记会被影响的节点种类很少。

    当存在某些量的存在情况很少的时候,往往可以从这里入手。

    对于这种和算法流程有关的题目,一定要仔细分析流程,一般可以基于流程来解决问题。

  • 相关阅读:
    June. 26th 2018, Week 26th. Tuesday
    June. 25th 2018, Week 26th. Monday
    June. 24th 2018, Week 26th. Sunday
    June. 23rd 2018, Week 25th. Saturday
    June. 22 2018, Week 25th. Friday
    June. 21 2018, Week 25th. Thursday
    June. 20 2018, Week 25th. Wednesday
    【2018.10.11 C与C++基础】C Preprocessor的功能及缺陷(草稿)
    June.19 2018, Week 25th Tuesday
    June 18. 2018, Week 25th. Monday
  • 原文地址:https://www.cnblogs.com/Go7338395/p/13898872.html
Copyright © 2020-2023  润新知