题解:BZOJ2873 光之大陆

BZOJ2873 光之大陆

题目描述

给定 \(n\) 个点:

  1. \(n\) 个点划分成几个简单环(一个点可以成一个环,两个点不能成环)。

  2. 将这些环连接成树。

求方案数 \(\mod m\)

\(n\le 200\)\(m \le 10^6\)

思路

Part 1

\(f(i,j)\) 表示将 \(i\) 个点分成 \(j\) 个环的方案数,\(g(i)\) 表示 \(i\) 个点组成一个环的方案数。

  • \(g(1)=1\)

  • \(g(2)=0\)

  • \(g(i)=\frac{(i-1)}{2}\)\((i\ge 3)\)\((i-1)!\) 是圆排列,除以 \(2\) 是因为一个环和它翻转之后的环算同一个方案。

  • 那么 \(f(i,j)=\sum_{k=1}^{i-j+1}f(i-k,j-1)\times \binom{i-1}{k-1}\times g(k)\),(为什么组合数不是 \(\binom{i}{k}\)?因为我们考虑的是将 \(i\) 这个点放在哪个环,也就是 \(i\) 是必选的,如果这么写的话,答案会算重)。

Part 2

考虑这样一个问题:

给定 \(n\) 个点,已经这 \(n\) 个点组成的 \(k\) 个连通块的大小,每个连通块的组成都是固定的,求将这 \(k\) 个连通块连接成树的方案数。

把每个连通块看成一个点,假设每个连通块的度数为 \(d_i\)\(1\le i\le k\),连通块大小为 \(s_i\)

则接到每个连通块上的边的方案数为 \(s_i^{d_i}\)

根据 Prufer序列 的知识,我们可以得到,总方案数为 \(\sum\limits_{d_i\ge 1, \sum_{i=1}^{k}d_i=2(k-1)}\binom{k-2}{d_1-1,d_2-1,\dots,d_k-1}\times \prod\limits_{i=1}^{k}s_i^{d_i}\)

\(c_i = d_i-1\),原式变成,\(\sum\limits_{c_i\ge 0, \sum_{i=1}^{k}c_i=k-2}\binom{k-2}{c_1,c_2,\dots,c_k}\times \prod\limits_{i=1}^{k}s_i^{c_i+1}\)

根据多元二项式定理,化简得,\((\sum\limits_{i=1}^{k}s_i)^{k-2}\times \prod\limits_{i=1}^{k}s_i\)

于是等于,\(n^{k-2} \times \prod\limits_{i=1}^{k}s_i\)

那么根据这个式子,假设我们知道了每个环的大小,就很容易求出总方案数,但是我们不可能对于每种方案,都枚举一遍每个环的大小。怎么办呢?

注意 \(f\) 的转移方程,每次转移会枚举一个 \(k\),表示当前包含 \(i\) 的环一共分配多少个点,那么我们可以把这个式子 \(\prod\limits_{i=1}^{k}s_i\) 放在 \(f\) 里面一起计算,\(f\) 的转移方程就变为 \(f(i,j)=\sum_{k=1}^{i-j+1}f(i-k,j-1)\times \binom{i-1}{k-1}\times g(k)\times k\)

那么最终答案就是,\(\sum\limits_{i=1}^{n} f(n,i)\times n^{i-2}\)

注意事项

\(m\) 不一定是质数,所以除法的时候不一定存在逆元,但是仔细想想会发现可以将除法的操作转换掉,具体见代码的注释。

代码

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using ULL = unsigned long long;

const int N = 2e2 + 5;

int n, MOD;
int C[N][N], f[N][N], g[N];

int main() {
    cin >> n >> MOD;
    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] + C[i - 1][j]) % MOD;
        }
    }
    g[1] = 1, g[3] = 1; // 省去除以2的操作
    for (int i = 4; i <= n; i++) g[i] = 1LL * g[i - 1] * (i - 1) % MOD;
    f[0][0] = 1;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= i; j++) {
            for (int k = 1; k <= i - j + 1; k++) {
                f[i][j] = (f[i][j] + 1LL * f[i - k][j - 1] * C[i - 1][k - 1] % MOD * g[k] * k % MOD) % MOD;
            }
        }
    }
    int ans = g[n], p = 1; // 省去除以n的操作
    for (int i = 2; i <= n; i++) {
        ans = (ans + 1LL * f[n][i] * p) % MOD;
        p = p * n % MOD;
    }
    cout << ans << '\n';
    return 0;
}
posted @ 2025-02-05 15:57  chenwenmo  阅读(21)  评论(1)    收藏  举报