zoukankan      html  css  js  c++  java
  • [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;
    }
    

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

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

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

  • 相关阅读:
    java getEnv不区分大小写 getProperty区分大小写
    spring 解析配置文件问题
    (转载)Java里快如闪电的线程间通讯
    quartz中关键类
    HTTP中缓存相关
    (转载)javascript函数作用域和提前声明
    (转载)ETL利器Kettle实战应用解析系列一【Kettle使用介绍】
    rcnn 理解笔记
    打乱图片顺序,按一定比例分别存放
    Python 批量读取文件夹下的图片,并保存在文档下
  • 原文地址:https://www.cnblogs.com/Go7338395/p/13898872.html
Copyright © 2011-2022 走看看