「Solution」P6478 [NOI Online #2 提高组] 游戏
Problem
Link
题意:
有一棵包含 \(n=2m\) 个节点的有根树(以 \(1\) 为根)。每次选择之前没有选过的两个节点 \(u,v\) ,若 \(u\) 是 \(v\) 的祖先或 \(v\) 是 \(u\) 的祖先则视为非平局,否则为平局。求非平局回合数为 \(k =0,1,2...,n\) 的方案数。
Solution
首先,可以知道题目要求非平局回合数恰好为 \(k\) 的方案数。由于比较困难,所以我们可以先考虑求至少为 \(k\) 的方案数。
记恰好为 \(k\) 的方案数为 \(g(k)\) ,至少为 \(k\) 的方案数为 \(f(k)\) 。
不难得到:
\[f(k)=\sum\limits_{i=k}^n \binom{i}{k} g(i) \]这就是二项式反演的推广式,因此可以得到下式:
\[g(k)=\sum\limits_{i=k}^n (-1)^{i-k} \binom{i}{k} f(i) \]所以想办法求到 \(f\) 即可。
定义 \(dp[u][i]\) 表示以 \(u\) 为根的子树中,存在 \(i\) 对不同色节点( \(0,1\) 分别表示一种颜色)并且存在祖先和后代关系(其中一个为另一个的祖先)。
-
将 \(u\) 的儿子节点 \(v\) 的子树合并到 \(u\) 为根的子树。
枚举 \(u\) , \(v\) 子树的贡献即可。
-
\(u\) 结点产生了贡献。
则 \(u\) 的子树中每个还没被选择的异色节点都可以与 \(u\) 搭配。
转移 \(1\) 是在合并的过程中进行,转移 \(2\) 则则是在合并完之后进行。
所以最后需要用到的就是 \(dp[1][k]\) 。
我们让树中的 \(k\) 对点有了祖先与后代的关系,对于剩下 \(n-k\) 对点随意搭配就可以(不在乎这些是否满足关系)得到 \(f(k)\) 。
\[f(k)=dp[1][k]*(n-k)! \]为什么随意搭配的方案数为 \((n-k)!\) 呢?
题目中提到:两种情况不同当且仅当存在一个小 A 拥有的点 \(x\),小 B 在 \(x\) 被小 A 选择的那个回合所选择的点不同
即点对是无序的。所以对于 \((x,y)\) 来说有 \(n\) 个不同的 \(x\) 和 \(y\) ,可以固定 \(x\) ,然后用 \(y\) 来排列去匹配 \(x\) ,这样就可以保证没有重复的。
然后就结了。
Code
#include
#include
#include
using namespace std;
#define Maxn 5005
#define LL long long
#define Mod 998244353
#define rep(i, j, k) for(int i = (j); i <= (k); i ++)
#define per(i, j, k) for(int i = (j); i >= (k); i --)
char s[Maxn];
int n, tot = 1;
LL inv[Maxn], jc[Maxn];
LL ret[Maxn], dp[Maxn][Maxn], f[Maxn];
int head[Maxn], ver[Maxn << 1], nxt[Maxn << 1], vis[Maxn], siz[Maxn], cntb[Maxn];
void Link (int u, int v) {
ver[++ tot] = v;
nxt[tot] = head[u];
head[u] = tot;
}
LL Pow (int x, int y) {
LL ans = 1;
for (int i = y; i; i >>= 1) {
if (i & 1) ans = ans * x % Mod;
x = (LL) x * x % Mod;
}
return ans;
}
void Init (int N) {
jc[0] = 1;
rep (i, 1, N) jc[i] = jc[i - 1] * i % Mod;
inv[1] = 1;
rep (i, 2, N) inv[i] = (Mod - Mod / i) * inv[Mod % i] % Mod;
inv[0] = 1;
rep (i, 1, N) inv[i] = inv[i - 1] * inv[i] % Mod;
}
LL C (int x, int y) {
if (x < y) return 0;
return jc[x] * inv[y] % Mod * inv[x - y] % Mod;
}
void DP (int u, int lst) {
cntb[u] = vis[u];
dp[u][0] = siz[u] = 1;
for (int i = head[u]; i; i = nxt[i]) {
int v = ver[i];
if (lst == (i ^ 1)) continue;
DP (v, i);
rep (j, 0, siz[u] + siz[v]) ret[j] = 0;
rep (j, 0, min (siz[u], n / 2)) {
if (!dp[u][j]) continue;
rep (k, 0, min (siz[v], n / 2)) {
if (!dp[v][k]) continue;
ret[j + k] += dp[u][j] * dp[v][k] % Mod;
ret[j + k] %= Mod;
}
}
siz[u] += siz[v];
cntb[u] += cntb[v];
rep (j, 0, siz[u]) dp[u][j] = ret[j];
}
if (vis[u]) {
per (i, min (siz[u] - cntb[u], cntb[u]), 1) {
dp[u][i] += dp[u][i - 1] * (siz[u] - cntb[u] - (i - 1)) % Mod;
dp[u][i] %= Mod;
}
} else {
per (i, min (siz[u] - cntb[u], cntb[u]), 1) {
dp[u][i] += dp[u][i - 1] * (cntb[u] - (i - 1)) % Mod;
dp[u][i] %= Mod;
}
}
}
int main () {
// freopen ("match.in", "r", stdin);
// freopen ("match.out", "w", stdout);
scanf ("%d %s", &n, s + 1);
Init (5000);
rep (i, 1, n) vis[i] = s[i] - '0';
rep (i, 1, n - 1) {
int u, v;
scanf ("%d %d", &u, &v);
Link (u, v);
Link (v, u);
}
DP (1, 1e9);
rep (i, 0, n / 2) {
dp[1][i] = dp[1][i] * jc[n / 2 - i] % Mod;
}
rep (i, 0, n / 2) {
int op = -1;
rep (j, i, n / 2) {
op *= -1;
f[i] = (f[i] + op * C (j, i) * dp[1][j] % Mod + Mod) % Mod;
}
printf ("%lld\n", f[i]);
}
return 0;
}