[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;
}

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

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

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

原文地址:https://www.cnblogs.com/Go7338395/p/13898872.html