[NOI2020]命运

显然直接计数是不好计的,只能从 (dp) 这个角度来下手。

首先用最原始最直接的方法,直接在 (dp) 的过程中满足题目的要求。

既然问题给在一棵树上,那么必然和树脱不了关系,因此我们应该从树形 (dp) 的角度下手。

因为在树形 (dp) 的过程中我们只能考虑父子边的选择情况,那么对于一个点 (u),那些限制链顶在 (u) 子树内的限制链显然在其父亲 (fa) 进行转移时是考虑不到的,因此设计状态的时候必然要使得这些链已经被满足。

那么再来考虑这些链底在 (u) 子树内,链顶在 (u) 的祖先上的这些链。

不难发现只需要满足的链是链顶深度最深的这条链,因为只要满足了它其他的链也会被满足。

所以我们在 (dp) 的时候只关注于当前伸出去的链中链顶最深的链,于是我们的 (dp) 状态就呼之欲出了。

考虑令 (dp_{i, j}) 表示以 (i) 为根的子树内,还没有被满足限制的链中链顶伸出去的链的最深的链顶深度为 (j) 时的方案,特别地 (j = 0) 时表示没有伸上来的链。

那么转移的时候只要考虑 (u ightarrow v) 这条父子边选 (0 / 1) 即可。

(1) 时,(v) 伸上来的所有链都会被满足,于是有转移:

[dp_{u, i} imes sumlimits dp_{v, j} ightarrow dp_{u, i} ]

(0) 时,只需要考虑当前这个最深的链顶是否来自于 (v) 即可。

不来自于 (v) :

[dp_{u, i} imes sumlimits_{j = 0} ^ i dp_{v, j} ightarrow dp_{u, i} ]

来自于 (v)

[dp_{v, i} sumlimits_{j = 0} ^ i dp_{u, j} ightarrow dp_{u, i} ]

需要注意深度相同时 (dp_{u, i} imes dp_{v, i} ightarrow dp_{u, i}) 被计算了两次,需要减去。

但这样依然不能通过本题,怎么办呢?

可以发现,这个 (dp) 的状态量就惊人地达到了 (O(n ^ 2)),因此我们必须要从状态这里下手。

这时候我们引入一个叫做整体 (dp) 的技巧,其适用范围往往是存在一个维度上初始值并不多的情况。

其做法就是将这一个需要优化的维度放到动态开点线段树上,然后可以通过主席树或线段树合并一类技巧来优化这个 (dp) 的流程。

那么在本题当中,我们可以将第二维放到动态开点线段树上。

那么对于第一条转移,相当于是给 (u) 这个线段树上每个位置乘上 (v) 中所有数之和,不难发现这可以直接在线段树上完成。

对于第二条转移,相当于是对于 (u) 所在线段树上每个非 (0) 的点乘上 (v) 所在线段树上 (u) 左侧所有位置之和,不难发现这个操作是可以在线段树合并的时候同时完成的,只需要递归时记录当前区间左侧所有数之和即可。

对于第三条转移,本质上与第二条转移没有区别。

因此这个转移的过程就被我们在线段树合并的同时优化掉了,复杂度 (O(n log n))

#include <bits/stdc++.h>
using namespace std;
#define ls t[p].l
#define rs t[p].r
#define mid (l + r >> 1)
#define rep(i, l, r) for (int i = l; i <= r; ++i)
#define Next(i, u) for (int i = h[u]; i; i = e[i].next)
const int N = 500000 + 5;
const int Mod = 998244353;
struct tree { int sum, tag, l, r;} t[N * 40];
struct edge { int v, next;} e[N << 1];
int n, m, u, v, tot, cnt, h[N], rt[N], dep[N], val[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 Dec(int a, int b) { return (a -= b) < 0 ? a + Mod : a;}
int Mul(int a, int b) { return 1ll * a * b % Mod;}
void add(int u, int v) {
    e[++tot].v = v, e[tot].next = h[u], h[u] = tot;
    e[++tot].v = u, e[tot].next = h[v], h[v] = tot;
}
void Prefix(int u, int fa) {
    dep[u] = dep[fa] + 1;
    Next(i, u) if(e[i].v != fa) Prefix(e[i].v, u);
}
void down(int p) {
    t[ls].sum = Mul(t[ls].sum, t[p].tag), t[rs].sum = Mul(t[rs].sum, t[p].tag);
    t[ls].tag = Mul(t[ls].tag, t[p].tag), t[rs].tag = Mul(t[rs].tag, t[p].tag);
    t[p].tag = 1;
}
void update(int &p, int l, int r, int x, int y, int k) {
    if(!p) p = ++cnt, t[p].tag = 1; t[p].sum += k;
    if(l == r) return;
    if(mid >= x) update(ls, l, mid, x, y, k);
    if(mid < y) update(rs, mid + 1, r, x, y, k);
}
void modify(int &p, int l, int r, int x, int y) {
    if(!p) p = ++cnt, t[p].tag = 1;
    if(l == r) { t[p].sum = 0; return;}
    down(p);
    if(mid >= x) modify(ls, l, mid, x, y);
    if(mid < y) modify(rs, mid + 1, r, x, y);
    t[p].sum = Inc(t[ls].sum, t[rs].sum);
}
void Merge(int &p, int k, int l, int r, int S1, int S2, int S) {
    if(!p || !k) {
        if(!p && !k) return;
        if(!p) p = p + k, t[p].sum = Mul(t[k].sum, S1), t[p].tag = Mul(t[p].tag, S1);
        else t[p].sum = Mul(t[p].sum, Inc(S2, S)), t[p].tag = Mul(t[p].tag, Inc(S2, S));
        return ;
    }
    if(l == r) { 
        S1 = Inc(S1, t[p].sum), S2 = Inc(S2, t[k].sum);
        int tmp = Mul(t[p].sum, t[k].sum);
        t[p].sum = Inc(Mul(t[k].sum, S1), Mul(t[p].sum, Inc(S, S2))); 
        t[p].sum = Dec(t[p].sum, tmp);
        return ;
    }
    down(p), down(k);
    Merge(rs, t[k].r, mid + 1, r, Inc(S1, t[ls].sum), Inc(S2, t[t[k].l].sum), S);
    Merge(ls, t[k].l, l, mid, S1, S2, S);
    t[p].sum = Inc(t[ls].sum, t[rs].sum);
}
int query(int p, int l, int r, int x, int y) {
    if(!p) return 0;
    if(l >= x && r <= y) return t[p].sum;
    down(p);
    int ans = 0;
    if(mid >= x) ans = Inc(ans, query(ls, l, mid, x, y));
    if(mid < y) ans = Inc(ans, query(rs, mid + 1, r, x, y));
    return ans;
}
void dfs(int u, int fa) {
    update(rt[u], 0, n, val[u], val[u], 1);
    Next(i, u) { 
        int v = e[i].v; if(v == fa) continue;
        dfs(v, u), Merge(rt[u], rt[v], 0, n, 0, 0, t[rt[v]].sum);
    }
    modify(rt[u], 0, n, dep[u], dep[u]);
}
int main() {
    n = read();
    rep(i, 1, n - 1) u = read(), v = read(), add(u, v);
    Prefix(1, 0);
    m = read();
    rep(i, 1, m) u = read(), v = read(), val[v] = max(val[v], dep[u]);
    dfs(1, 0);
    printf("%d", t[rt[1]].sum);
    return 0;
}

(dp) 状态已经超过要求后,如果存在某一维初始值比较少,整体 (dp) 不失一个好的选择。

GO!
原文地址:https://www.cnblogs.com/Go7338395/p/13819753.html