7010. 2021.03.13【2021省赛模拟】graph

\(n\)个点的图,每次随机两个点\(u,v\)(两者独立随机互不相干),连边\((u,v)\),问第一次联通的期望步数。

\(n\le 100\)


讲阳间的\(O(n^5)\)做法。

\(G_i\)表示保持在恰好\(i\)个联通块的期望时间,\(F_i\)为至少。求\(F_i\)之后容斥得到\(G_i\)

考虑把点划分成若干个块,保证块之间不连通,块内的点可以联通可以不联通。

如果求出了这个东西的答案,则因为\(F_n=\sum_{i=n}^N S(N,i)G_i\),斯特林反演得\(G_n=\sum_{i=n}^N s(N,i)F_i(-1)^{N-i}\)

对于一个划分的方案,贡献为\(\frac{1}{1-\frac{\sum a_i^2}{n^2}}\),其中\(a_i\)表示块大小。(可以看做后面的连边都要限制在每个块内,不发生块的合并,保持在这状态的期望步数)。

可以dp计算:设\(dp_{i,j,k}\)表示用了\(i\)个点形成了\(j\)个块,\(\sum a_i^2=k\)的贡献(方案数,这个方案数和概率无关,它是用来处理“至少”的)。然后\(F_j=\sum dp_{n,j,k}\frac{1}{1-\frac{k}{n^2}}\)

这个dp可以\(O(n^5)\)算。因为有值的地方不会很多,所以时间约等于\(O(n^4)\)

题解做法:

\(dp_{n,k}\)\(n\)个点进行了\(k\)步不连通的概率。

据此列出DP方程:

\[dp_{n,k}=\sum_{i=1}^{n-1}\sum_{j=0}^k\binom{n-1}{i-1}(1-dp_{i,j})\binom{k}{j}(\frac{i}{n})^{2j}(\frac{n-i}{n})^{2(k-j)} \]

然后它发现可以表示成\(dp_{n,k}=\sum_{i=0}^{n^2-1}c_{n,i}(\frac{i}{n^2})^k\)的形式,可以归纳证明。

讲真我觉如果是我,我肯定不会发现的。所以扩展性不太好。

根据DP方程得到\(c\)的递推式,算出\(c\)就好了,时间\(O(n^4)\)


using namespace std;
#include <bits/stdc++.h>
#define N 105
#define ll long long
int n,mo;
ll qpow(ll x,ll y=mo-2){
	ll r=1;
	for (;y;y>>=1,x=x*x%mo)
		if (y&1)
			r=r*x%mo;
	return r;
}
int dp[N][N*N];
int s[N][N],C[N][N];
ll f[N],g[N];
int main(){
	freopen("graph.in","r",stdin);
	freopen("graph.out","w",stdout);
//	freopen("in.txt","r",stdin);
	scanf("%d%d",&n,&mo);
	C[0][0]=1;
	for (int i=1;i<=n;++i){
		C[i][0]=1;
		for (int j=1;j<=i;++j)
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%mo;
	}
	s[0][0]=1;
	for (int i=1;i<=n;++i)
		for (int j=1;j<=i;++j)
			s[i][j]=(s[i-1][j-1]+(ll)s[i-1][j]*(i-1))%mo;
	dp[0][0]=1;
	for (int j=0;j<n;++j){
		static int _dp[N][N*N];
		memset(_dp,0,sizeof _dp);
		for (int i=j;i<n;++i)
			for (int k=0;k<=i*i;++k)
				if (dp[i][k]){
					int tmp=dp[i][k];
					for (int t=1;i+t<=n;++t)
						_dp[i+t][k+t*t]=(_dp[i+t][k+t*t]+(ll)tmp*C[i+t-1][t-1])%mo;
				}
		memcpy(dp,_dp,sizeof _dp);
		if (j+1>=2){
			for (int k=1;k<n*n;++k)
				if (dp[n][k])
					(f[j+1]+=(ll)dp[n][k]*n*n%mo*qpow(n*n-k))%=mo;
		}
	}
	for (int i=2;i<=n;++i)
		for (int j=i;j<=n;++j)
			(g[i]+=s[j][i]*f[j]%mo*(j-i&1?-1:1))%=mo;
	ll ans=0;
	for (int i=2;i<=n;++i)
		ans+=g[i];
	ans=(ans%mo+mo)%mo;
	printf("%lld\n",ans);
	return 0;
}
posted @ 2021-03-14 13:41  jz_597  阅读(94)  评论(0编辑  收藏  举报