Unbranched

简化题意

给定一个有 \(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\)

  1. 当前联通块为链。

\(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_{i, j} = f_{i, j} + f_{i - k, i - k + 1} * C_{n - i + k - 1}^{k - 1} * \frac{k!}{2} \]

  1. 当前连通块为环。

思路相似,直接给出式子:

\[f_{i, j} = f_{i, j} + f_{i - k, i - k} * C_{n - i + k - 1}^{k} * \frac{(k - 1)!}{2} \]

最后答案即为 \(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. 考试的时候要合理安排时间,不要只再同一题上思考超过 \(1\) 个小时。
posted @ 2025-09-29 21:02  wuzihenb  阅读(14)  评论(0)    收藏  举报