NEERC2006的题,有点难度,好题。
题目大意:给一个有n个点,m条边的无向图,两点之间可以存在多条边。现在每次随机增加一条边,问使得全部点都连通需要增加多少次(期望值)。
求期望的题,由于期望的线性性,自然想到dp
首先是状态表示,f(s1,s2,s3,...sn)表示当前合并到了有n个连通分量,每个连通分量有s1,s2,s3,...sn个点,且s1<=s2<=s3...<=sn。(最小表示)
则有两种转移:一种是连边后将两个连通分量合并了,另一种则不是。
前者有sigma(si*sj)(1<=i,j<=n,i!=j)种,后者则有sigma(si*(si-1)/2)(1<=i<=n)种。
令前者发生的概率为p,后者为q。
动态规划的转移方程为:f = p*(1+r) + p*q*(2+r) + p*q^2*(3+r) ....其中r为p发生后,新状态的期望值。f的极限为p*(1/(1-q)+r)/(1-q)。
注意,为什么r为p发生后,新状态的期望值,这是因为我是bottom-up的,所以目标状态期望值为0。
至于存储状态,可以hash,也可以暴力存储(最多6000个状态)
#include <iostream>
#include <algorithm>
using namespace std;
const int pow=31,base=10222223,maxn=30;
int h[base][maxn],t[maxn],s[maxn],fa[maxn];
double f[base];
bool vis[base];
int n,m,nn;
int find(int x)
{
if (fa[x]==x) return x;
fa[x]=find(fa[x]);
return fa[x];
}
int gethash(int t[])
{
int i,ans=0;
for (i=1;i<=t[0];i++)
ans=(ans+(ans*pow+t[i])%base)%base;
while (1)
{
if (!h[ans][0]) break;
bool ff=1;
for (i=0;i<=h[ans][0];i++)
if (h[ans][i]!=t[i])
{
ff=0;break;
}
if (ff) return ans;
ans=(ans<base)?ans++:0;
}
for (i=0;i<=t[0];i++)
h[ans][i]=t[i];
return ans;
}
int cmp(int a,int b) {return a>b;}
double dp(int s)
{
if (h[s][0]==1) return 0;
if (vis[s]) return f[s];
int i,j,k,ss,t[maxn];
double p=0,q=0,r,ans=0;
for (i=1;i<=h[s][0];i++)
q+=h[s][i]*(h[s][i]-1)*1.0/2/nn;
for (i=1;i<=h[s][0];i++)
for (j=i+1;j<=h[s][0];j++)
{
p=h[s][i]*h[s][j]*1.0/nn;
for (k=1;k<i;k++) t[k]=h[s][k];
for (k=i+1;k<j;k++) t[k-1]=h[s][k];
for (k=j+1;k<=h[s][0];k++) t[k-2]=h[s][k];
t[0]=h[s][0]-1;t[h[s][0]-1]=h[s][i]+h[s][j];
sort(t+1,t+h[s][0],cmp);
ss=gethash(t);
r=dp(ss);
ans+=p*(1/(1-q)+r)/(1-q);
}
vis[s]=1;f[s]=ans;
return ans;
}
int main()
{
freopen("pin.txt","r",stdin);
freopen("pou.txt","w",stdout);
cin >> n >> m;
int i,x,y;
for (i=1;i<=n;i++) fa[i]=i,s[i]=1;
for (i=0;i<m;i++)
{
cin >> x >> y;
x=find(x);y=find(y);
if (x==y) continue;
fa[y]=x;
s[x]=s[x]+s[y];s[y]=0;
}
nn=(n*(n-1))/2;
for (i=1;i<=n;i++)
if (s[i])
t[++t[0]]=s[i];
cout.precision(6);
cout.setf(ios_base::floatfield,ios_base::fixed);
cout << dp(gethash(t)) << endl;
x=0;
for (i=0;i<=base;i++)
x+=(h[i][0]>0);
cout << x;
}