简化题意
给定一个有 \(N\) 个顶点、\(M\) 条边的无向图,顶点有标签,边没有标签。该图不一定是简单图,也不一定是连通图。请计算满足以下条件的图的数量,并将结果对 \(10^9+7\) 取模:
- 不包含自环。
- 所有顶点的度数都不超过 \(2\)。
- 将每个连通分量的大小按升序排列后,最大值恰好为 \(L\)。
思路
题目分析
- 所有点的度数都不超过 \(2\)。
所以每一个联通块要么是一条链,要么是一个环。
- 所有的联通分量的最大值恰好为 \(L\)。
直接维护恰好为 \(L\) 不好维护,可以先求出联通分量 \(\le L\) 的方案数和 \(< L\)的方案数,再相减就是答案了。这是一个非常常见的一个小 trick。
解决问题
注意到和方案数有关,考虑动态规划dp。
设 \(f_{i, j}\) 表示用了 \(i\) 个点,\(j\) 条边,最大的联通分量 \(\le L\) 的方案数。
设 \(g_{i, j}\) 表示用了 \(i\) 个点,\(j\) 条边,最大的联通分量 \(< L\) 的方案数。
不妨枚举 \(k\),表示当前的联通分量为 \(k\)。
- 当前联通块为链。
则 \(f_{i, j}\) 可以从 \(f_{i - k, i - k + 1}\) 转移得到,当前联通块的点的选择数量为 \(n - i + k\),但是因为 \({1, 2, 3} {4, 5, 6}\) 和 \({4, 5, 6} {1, 2, 3}\) 这两种划分只会算一种,不妨每次都要取走至少一个剩下的点的最小值,因为 \(1 < 3\),所以会先选 \({1, 2, 3} {4, 5, 6}\) 这中情况,而不会选 \({4, 5, 6} {1, 2, 3}\) 这种情况,则选择方案数为 \(C_{n - i + k - 1}^{k - 1}\),由于 \({1, 2, 3}\) 和 \({1, 3, 2}\) 算两种,所以还需要乘上 \(k!\),但是又因为 \({3, 2, 1}\) 和 \({1, 2, 3}\) 算同种方案,所以还需要除以二,所以转移式如下:
- 当前连通块为环。
思路相似,直接给出式子:
最后答案即为 \(f_{n, m}\) - \(g_{n, m}\)。
代码
#include <iostream>
using namespace std;const long long N = 310;
const long long mod = 1e9 + 7;
long long n, m, l, f[N][N], g[N][N], prep[N], inv[N];long long qmi(long long x, long long k) {long long res = 1;while (k) {if (k & 1)res = res * x % mod;k >>= 1;x = x * x % mod;}return res;
}long long C(long long x, long long y) { return prep[x] % mod * inv[x - y] % mod * inv[y] % mod; }int main() {ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin >> n >> m >> l;prep[0] = prep[1] = 1;for (long long i = 2; i <= n; i++) prep[i] = prep[i - 1] * i % mod;for (long long i = 0; i <= n; i++) inv[i] = qmi(prep[i], mod - 2);f[0][0] = 1;g[0][0] = 1;for (long long i = 1; i <= n; i++)for (long long j = 0; j <= m; j++) {for (long long k = 1; k <= min(l, min(i, j + 1)); k++) {f[i][j] = (f[i][j] + f[i - k][j - k + 1] % mod * C(n - i + k - 1, k - 1) % mod * (k == 1 ? 1 : inv[2]) % mod * prep[k] % mod) % mod;}for (long long k = 2; k <= min(l, min(i, j)); k++) {f[i][j] = (f[i][j] + f[i - k][j - k] % mod * C(n - i + k - 1, k - 1) % mod * (k == 2 ? 1 : inv[2]) % mod * prep[k - 1] % mod) % mod;}for (long long k = 1; k <= min(l - 1, min(i, j + 1)); k++) {g[i][j] = (g[i][j] + g[i - k][j - k + 1] % mod * C(n - i + k - 1, k - 1) % mod * (k == 1 ? 1 : inv[2]) % mod * prep[k] % mod) % mod;}for (long long k = 2; k <= min(l - 1, min(i, j)); k++) {g[i][j] = (g[i][j] + g[i - k][j - k] % mod * C(n - i + k - 1, k - 1) % mod * (k == 2 ? 1 : inv[2]) % mod * prep[k - 1] % mod) % mod;}}cout << (f[n][m] - g[n][m] + mod) % mod << "\n";return 0;
}
个人反思
考场的思路
时间不够,没有看题。
怎么改正
- 考试的时候要合理安排时间,不要只再同一题上思考超过 \(1\) 个小时。