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\) 必然合法,故
前缀和优化即可 \(O(1)\) 转移。设 \(s_u\) 为 \(f_u\) 的前缀和。
若 \(u\) 在时刻 \(k\) 已经是非关键点了,枚举其在第 \(p\) 次操作后变为了非关键点,则在 \(p+1 \sim k\) 次操作中 \(u\) 有且仅有一个儿子子树中有关键点。枚举这个关键点所在的子树,容易得到转移
预处理 \(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;
}