How Many of Them(计数dp)

题目:洛谷P6596

题目描述:

求满足如下条件的无向连通图的数量:

  1. \(n\)个结点构成,结点有标号
  2. 割边不超过\(m\)
  3. 没有重边和自环

答案对\(10^9+7\)取模

\(n \leq 50\)\(m \leq \frac{n(n-1)}{2}\)

蒟蒻题解:

容易发现,对于一个图中,割边数量最多为\(n-1\)条(树的情况)

这样如果\(m \geq n\),那么可以令\(m = n - 1\)

对于题目要求的\(3\)个限制中,主要的限制为第\(2\)条,限制割边的数量

对于一个图,如果没有割边,那么这个图就是一个边双连通分量,我们可以把一个有\(i\)个点,\(j\)条割边的图压成一个仅有\(j+1\)个点,\(j\)条边的树,树上的每个点为一个边双连通分量

不妨设\(f_{i,j}\)表示有\(i\)个点,\(j\)条割边的方案数

由于结点有标号,我们考虑找这\(i\)个点中结点标号最小的点,将它所在的边双连通分量删除后,这个图会变成好几个连通块

不妨再延伸一维,设\(g_{i,j,k}\)表示有\(i\)个点,\(j\)个连通块,\(k\)条割边的方案数,这样我们计算\(f_{i,j}(j>0)\)时可以枚举删掉一个包含结点标号最小的点的边双连通分量后会分成几个连通块

\[f_{i,j} = \sum_{k=1}^{i-1} f_{k,0} \binom{i-1}{k-1} \sum_{l=1}^j g_{i-k,l,j-l}S \]

其中\(S\)表示的是这\(l\)个连通块分别找一个点和要删除的边双连通分量中的一个点连边的所有方案

一条边枚举两个点,对于要删除的边双连通分量上的点,方案显然是\(k\),而对于连通块内的点就不好计算了

我们改一下\(g\)的定义,设\(g_{i,j,k}\)表示有\(i\)个点,\(j\)个连通块,\(k\)条割边,且每个连通块内都选择了一个点的方案数

这样我们就可以得到:

\[f_{i,j} = \sum_{k=1}^{i-1} f_{k,0} \binom{i-1}{k-1} \sum_{l=1}^j g_{i-k,l,j-l}k^l \]

至于\(f_{i,0}\),它没有割边,它本身就是一个双连通分量,要计算它的方案数,我们可以考虑容斥,将所有方案数扣去不合法的方案数:

\[f_{i,0} = h_{i} - \sum_{j=1}^{i-1}f_{i,j} \]

其中\(h_{i}\)表示\(i\)个点的无向连通图的方案数,再考虑容斥,可以用总方案数扣去不连通的方案数:

\[h_i = 2^{\frac{i(i-1)}{2}} - \sum_{j=1}^{i-1} h_j \binom{i-1}{j-1} 2^{\frac{(i-j)(i-j-1)}{2}} \]

接下来考虑\(g\)要怎么算,每次可以删掉一个连通块,连通块的答案可以从\(f\)求来,再乘上这个连通块中选择一个点的方案数,再乘上删掉这个连通块后剩下的方案数:

\[g_{i,j,k} = \sum_{l=1}^i \sum_{r=0}^k f_{l,r} l \binom{i - 1}{l - 1} g_{i-l,j-1,k-r} \]

综上,我们可以整理一下:

\[f_{i,j} = \sum_{k=1}^{i-1} f_{k,0} \binom{i-1}{k-1} \sum_{l=1}^j g_{i-k,l,j-l}k^l \]

\[h_i = 2^{\frac{i(i-1)}{2}} - \sum_{j=1}^{i-1} h_j \binom{i-1}{j-1} 2^{\frac{(i-j)(i-j-1)}{2}} \]

\[f_{i,0} = h_{i} - \sum_{j=1}^{i-1}f_{i,j} \]

\[g_{i,j,k} = \sum_{l=1}^i \sum_{r=0}^k f_{l,r} l \binom{i - 1}{l - 1} g_{i-l,j-1,k-r} \]

时间复杂度\(\mathcal O(n^5)\)

参考程序:

#include<bits/stdc++.h>
using namespace std;
#define Re register int
typedef long long ll;

const int N = 55, p = 1e9 + 7;
int n, m, ans, fac[N], inv[N], pw2[N * N], pw[N][N], h[N], f[N][N], g[N][N][N];

inline int inc(int x, int y)
{
	x += y;
	return x < p ? x : x - p;
}

inline int sub(int x, int y)
{
	x -= y;
	return x < 0 ? x + p : x;
}

inline int fpow(ll x, int y)
{
	ll z = 1;
	while (y)
	{
		if (y & 1) z = z * x % p;
		x = x * x % p, y >>= 1;
	}
	return z;
}

inline int C(int x, int y)
{
	return 1ll * fac[x] * inv[y] % p * inv[x - y] % p;
}

int main()
{
	scanf("%d %d", &n, &m);
	if (m >= n) m = n - 1;
	g[0][0][0] = fac[0] = pw2[0] = 1;
	for (Re i = 1; i <= n; ++i)
	{
		pw[i][0] = 1, fac[i] = 1ll * fac[i - 1] * i % p;
		for (Re j = 1; j <= n; ++j) pw[i][j] = 1ll * pw[i][j - 1] * i % p;
	}
	inv[n] = fpow(fac[n], p - 2);
	for (Re i = n; i; --i) inv[i - 1] = 1ll * inv[i] * i % p;
	for (Re i = 1; i <= n * (n - 1) / 2; ++i) pw2[i] = inc(pw2[i - 1], pw2[i - 1]);
	for (Re i = 1; i <= n; ++i)
	{
		h[i] = pw2[i * (i - 1) / 2];
		for (Re j = 1; j < i; ++j) h[i] = sub(h[i], 1ll * h[j] * pw2[(i - j) * (i - j - 1) / 2] % p * C(i - 1, j - 1) % p) % p;
		for (Re j = 1; j < i; ++j)
			for (Re k = 1; k <= i - j; ++k)
			{
				int u = 0;
				for (Re l = 1; l <= j; ++l) u = (1ll * g[i - k][l][j - l] * pw[k][l] + u) % p;
				f[i][j] = (1ll * f[k][0] * C(i - 1, k - 1) % p * u + f[i][j]) % p;
			}
		f[i][0] = h[i];
		for (Re j = 1; j < i; ++j) f[i][0] = sub(f[i][0], f[i][j]);
		for (Re j = 1; j <= i; ++j)
			for (Re k = 0; k <= i - j; ++k)
				for (Re l = 1; l <= i; ++l)
					for (Re r = 0; r <= k; ++r) g[i][j][k] = (1ll * f[l][r] * C(i - 1, l - 1) % p * l % p * g[i - l][j - 1][k - r] + g[i][j][k]) % p;
	}
	for (Re i = 0; i <= m; ++i) ans = inc(ans, f[n][i]);
	printf("%d", ans);
	return 0;
}
posted @ 2021-06-03 10:35  clfzs  阅读(123)  评论(0)    收藏  举报