[dp,prufer序列]CF917D Stranger Trees


\(\texttt{link}\)

有一个经典结论: 对于一个包含 \(n\) 个点的无向图,若连通块个数为 \(k\) ,大小分别为 \(s_1,...,s_k\),则用 \(k-1\) 条边将 \(k\) 个连通块连起来的方案数为 \(n^{k-2}\prod\limits_{i=1}^k s_i\)

\(dp(i,j,k)\) 表示子树 \(i\) 分成 \(j\) 个连通块,点 \(i\) 所在连通块大小为 \(k\) 的方案数,类似树上背包合并可以做到 \(\mathrm{O(n^3)}\),但是可以做到更优。

考虑一个转化:\(\prod\limits_{i=1}^ks_i\) 等价于在每个块中选一个点的方案数

于是可以设 \(dp(i,j,0/1)\) 表示子树 \(i\) 分成 \(j\) 个连通块,点 \(i\) 所在连通块是否选了一个点的方案数,类似树上背包合并就可以做到 \(\mathrm{O(n^2)}\) 了。

计算答案二项式反演即可。

\(\texttt{Code:}\)

#include 
#define pb push_back
#define fi first
#define se second

using namespace std;

const int cmd = 1e9 + 7;
const int N = 8e3 + 5;

int fpow(int a, int b) {
    int res = 1;
    for (; b; b >>= 1, a = 1ll * a * a % cmd)
        if (b & 1) res = 1ll * res * a % cmd;
    return res;
}

int add(int a, int b) {a += b; return a < cmd ? a : a - cmd;}

int sub(int a, int b) {a -= b; return a < 0 ? a + cmd : a;}

int n, sb[N][2], fac[N], ifac[N], s[N], pw[N];

vector g[N];

vector dp[N][2];

void dfs(int u, int fa) {
    s[u] = 1;
    for (int i = 0; i < (int)g[u].size(); i++) {
        int v = g[u][i];
        if (v == fa) continue;
        dfs(v, u);
        s[u] += s[v];
    }
    dp[u][0].resize(s[u] + 1);
    dp[u][1].resize(s[u] + 1);
    dp[u][0][1] = dp[u][1][1] = 1;
    s[u] = 1;
    for (int i = 0; i < (int)g[u].size(); i++) {
        int v = g[u][i];
        if (v == fa) continue;
        for (int i = 1; i <= s[u] + s[v]; i++) sb[i][0] = sb[i][1] = 0;
        for (int i = 1; i <= s[u]; i++)
        for (int j = 1; j <= s[v]; j++) {
            sb[i + j][0] = add(sb[i + j][0], 1ll * dp[u][0][i] * dp[v][1][j] % cmd);
            sb[i + j][1] = add(sb[i + j][1], 1ll * dp[u][1][i] * dp[v][1][j] % cmd);
            sb[i + j - 1][0] = add(sb[i + j - 1][0], 1ll * dp[u][0][i] * dp[v][0][j] % cmd);
            sb[i + j - 1][1] = add(sb[i + j - 1][1], (1ll * dp[u][1][i] * dp[v][0][j] + 1ll * dp[u][0][i] * dp[v][1][j]) % cmd);
        }
        for (int i = 1; i <= s[u] + s[v]; i++)
            dp[u][0][i] = sb[i][0], dp[u][1][i] = sb[i][1];
        s[u] += s[v];
    }
}

int C(int n, int m) {
    return 1ll * fac[n] * ifac[m] % cmd * ifac[n - m] % cmd;
}

int main() {
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout);
    scanf("%d", &n);
    if (n == 1) {puts("1"); return 0;}
    for (int i = 1; i < n; i++) {
        int u, v; scanf("%d%d", &u, &v);
        g[u].pb(v); g[v].pb(u);
    }
    dfs(1, 0);
    fac[0] = ifac[0] = 1;
    for (int i = 1; i <= n; i++) {
        fac[i] = 1ll * fac[i - 1] * i % cmd;
        ifac[i] = fpow(fac[i], cmd - 2);
    }
    pw[0] = 1;
    for (int i = 1; i <= n; i++) pw[i] = 1ll * pw[i - 1] * n % cmd;
    for (int i = 0; i < n; i++) {
        int ans = 0;
        for (int j = i; j < n; j++) {
            int cur;
            if (n - j == 1) cur = C(j, i);
                else cur = 1ll * C(j, i) * dp[1][1][n - j] % cmd * pw[n - j - 2] % cmd;
            if ((j - i) & 1) ans = sub(ans, cur);
                else ans = add(ans, cur);
        }
        printf("%d ", ans);
    }
    return 0;
}