CF1707D Partial Virtual Trees【二项式反演,DP】

传送门

思路

肯定会想要 DP 算这个东西,但 \(S_1 \neq S_2\) 实在是一个很烦的限制,这导致 DP 的时候必须记录上一次选择的点集。考虑把这个限制容斥掉,具体来说,令 \(G_i\) 表示答案,\(F_i\) 表示使用 \(i\) 次操作使得 \(U = \{1\}\),但不要求 \(S_1 \neq S_2\) 的方案数。枚举 \(F_i\) 中有几次操作满足 \(S_1 \neq S_2\),可得 \(F_i = \sum_{j=1}^i \binom{i}{j} G_j\),根据二项式反演有 \(G_i = \sum_{j=1}^i (-1)^{i-j} \binom{i}{j} F_j\),于是我们现在的目标变成了求出 \(F_i\)

一个 naive 的想法是设 \(f_{u,k}\) 表示 \(u\) 子树在使用了第 \(k\) 次操作后恰好变为 \(\{u\}\) 的方案数,但......由于 \(u\) 在虚树上的儿子可能是子树中的任何一个点,巨大的枚举代价无法避免。不过这给了我们启示,设 \(f_{u,k}\) 表示 \(u\) 子树内直到第 \(k\) 次操作还有关键点的方案数,转移考虑两种情况:

\(u\) 在时刻 \(k\) 仍然是关键点,显然只要每个子树合法则 \(u\) 必然合法,故

\[f_{u,k} \gets \prod_{v \in son_u} \sum_{i = 0}^k f_{v,i} \]

前缀和优化即可 \(O(1)\) 转移。设 \(s_u\)\(f_u\) 的前缀和。

\(u\) 在时刻 \(k\) 已经是非关键点了,枚举其在第 \(p\) 次操作后变为了非关键点,则在 \(p+1 \sim k\) 次操作中 \(u\) 有且仅有一个儿子子树中有关键点。枚举这个关键点所在的子树,容易得到转移

\[f_{u,k} \gets \sum_{v \in son_u} f_{v,k} \sum_{p=0}^{k-1} \prod_{r \in son_u \land r \neq v} s_{r,p} \]

预处理 \(s\) 的前后缀积及前后缀积的前缀和即可 \(O(1)\) 转移。因此 DP 部分的复杂度为 \(O(n^2)\),最后 \(O(n^2)\) 反演回去即可。

Code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
#define int long long
#define mem(x, v, n) memset(x, v, sizeof(int) * (n))
#define mcpy(x, y, n) memcpy(x, y, sizeof(int) * (n))
#define lob lower_bound
#define upb upper_bound
using namespace std;

inline int read() {
	int x = 0, w = 1;char ch = getchar();
	while (ch > '9' || ch < '0') { if (ch == '-')w = -1;ch = getchar(); }
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return x * w;
}

inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }

const int MN = 2e3 + 5;
const int Mod = 998244353;

inline int qPow(int a, int b = Mod - 2, int ret = 1) {
    while (b) {
        if (b & 1) ret = ret * a % Mod;
        a = a * a % Mod, b >>= 1;
    }
    return ret;
}

// #define dbg

int N, P, f[MN][MN], s[MN][MN], g[MN][MN], F[MN], C[MN][MN], ans[MN];
vector <int> G[MN], son[MN];

inline void Add(int &x, int y) {
    x += y; if (x >= P) x -= P;
}
inline void DFS(int u, int pr) {
    f[u][0] = s[u][0] = 1;
    for (int v : G[u]) {
        if (v == pr) continue;
        son[u].pb(v);
        DFS(v, u);
    }
    for (int k = 0; k <= N; k++) {
        int prod = 1;
        for (int v : son[u]) prod = prod * s[v][k] % P;
        static int pre[MN], suf[MN];
        int L = son[u].size();
        pre[0] = suf[L + 1] = 1;
        for (int i = 1; i <= L; i++) {
            pre[i] = pre[i - 1] * s[son[u][i - 1]][k] % P;
        }
        for (int i = L; i; i--) {
            suf[i] = suf[i + 1] * s[son[u][i - 1]][k] % P, 
            g[son[u][i - 1]][k] = pre[i - 1] * suf[i + 1] % P;
        }
    }
    for (int v : son[u])
        for (int k = 1; k <= N; k++)
            Add(g[v][k], g[v][k - 1]);
    for (int k = 1; k <= N; k++) {
        int prod = 1;
        for (int v : son[u]) prod = prod * s[v][k] % P;
        Add(f[u][k], prod);
        for (int v : son[u]) Add(f[u][k], f[v][k] * g[v][k - 1] % P);
    }
    for (int k = 1; k <= N; k++)
        s[u][k] = s[u][k - 1], Add(s[u][k], f[u][k]);
}

signed main(void) {
    N = read(), P = read();
    for (int i = 0; i <= N; i++)
        for (int j = 0; j <= i; j++) {
            if (!j) C[i][j] = 1;
            else C[i][j] = C[i - 1][j - 1], Add(C[i][j], C[i - 1][j]);
        }
    for (int i = 1; i < N; i++) {
        int u = read(), v = read();
        G[u].pb(v);
        G[v].pb(u);
    }
    DFS(1, 0);
    for (int i = 1; i < N; i++) {
        F[i] = 1;
        for (int v : son[1]) F[i] = F[i] * s[v][i - 1] % P;
    }
    for (int i = 1; i < N; i++) {
        for (int j = 1; j <= i; j++) {
            int Coef = C[i][j] * F[j] % P;
            Add(ans[i], ((i - j) & 1) ? P - Coef : Coef);
        }
    }
    for (int i = 1; i < N; i++) printf("%lld%c", ans[i], " \n"[i == N - 1]);
    return 0;
}
posted @ 2022-07-22 17:09  came11ia  阅读(63)  评论(0编辑  收藏  举报