「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\) 分别表示一种颜色)并且存在祖先和后代关系(其中一个为另一个的祖先)。

  1. \(u\) 的儿子节点 \(v\) 的子树合并到 \(u\) 为根的子树。

    枚举 \(u\) , \(v\) 子树的贡献即可。

  2. \(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 <cstdio>
#include <cstring>
#include <algorithm>

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;
}
posted @ 2021-12-14 21:52  PoisonNNN  阅读(42)  评论(1编辑  收藏  举报