题解:BZOJ2873 光之大陆
题目描述
给定 \(n\) 个点:
-
将 \(n\) 个点划分成几个简单环(一个点可以成一个环,两个点不能成环)。
-
将这些环连接成树。
求方案数 \(\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;
}

浙公网安备 33010602011771号