Description
给出无向图G(V, E). 每次操作任意加一条非自环的边(u, v), 每条边的选择是等概率的. 问使得G连通的期望操作次数. (|V| <= 30, |E| <= 1000)
Input
第一行两个整数N,M 1<=N<=30 0<=M<=1000 接下来M行,每行两个整数X,Y表示两者之间已修好一条道路. 两点之间可以不止修了一条路,也有可能M条路已使N个点成为一个整体.
Output
输出一个小数,表示新修道路条数的期望值,保留六位小数.
中文题面善解人意。
先用并查集处理一下联通块的问题。
就按照期望来求:
设 v 为现在图的状态,那么就可以求出dp[v]。
尝试加一条边,可以让这个图的连通性不变,假设每个块的大小为 1 1 3
那么加一条边可以得到下面几个状态:
1 1 3 概率为p1,状态为1
2 3 概率为p2,状态为2
1 4 概率为p3,状态为3
那么就有 dp[v]=p1*dp[1]+p2*dp[2]+p3*dp[3]+1;这个加一就是加边操作。
但是怎么用一个值来表示一个状态。
二进制?不行。
直接用vector装入每个联通块的大小,然后用map映射就行了。
然后来考虑一下细节。
假如只有一个联通块,那么dp[]=0:
这里可以看出:联通块个数为 x的dp值肯定由 联通块个数为 x-1求出。
就是类似于求期望的逆推。
那么就枚举联通块的个数,然后DFS暴力生成每个块里面点的个数。然后生成一个vector,再转移一下就可以了。注意DFS时要加一点剪枝。
代码能力不好,所以不好看。
#include<stdio.h>
#include<algorithm>
#include<string>
#include<string.h>
#include<queue>
#include<vector>
#include<stack>
#include<math.h>
#include<map>
#include<iostream>
using namespace std;
#define maxn 35
map<vector<int>,double> dp;
int n,m,f[maxn],use[maxn],cnt;
vector<int> tmp,c;
double ret=0.0;
void inti()
{
memset(use,0,sizeof(use));
for(int i=1;i<=n;i++)
f[i]=i;
}
int findfa(int x)
{
if(x==f[x])
return x;
return f[x]=findfa(f[x]);
}
void dfs(int need,int tcnt,int tsum,int now)
{
if(tcnt>need||tsum>n||now>n)
return ;
int smax=now*(need-tcnt)+tsum;
if(smax>n) //剪枝
return ;
if(tsum==n&&tcnt==need)
{
if(dp[tmp]!=0)
return ;
ret=0.0;
c=tmp;
int ts=0;
int len=c.size();
for(int i=0;i<len;i++)
{
int x=c[i];
ts+=x*(x-1)/2;
}
for(int i=0;i<len;i++)
{
for(int j=0;j<i;j++) //枚举两个点
{
int x=c[i];
int y=c[j];
c.erase(c.begin()+i);
c.erase(c.begin()+j);
c.push_back(x+y);
sort(c.begin(),c.end());
ret+=(double)(x*y)/(double)(n*(n-1)/2)*dp[c];
for(int k=0;k<len-1;k++)
{
if(c[k]==x+y)
{
c.erase(c.begin()+k);
break;
}
}
c.push_back(x);
c.push_back(y);
sort(c.begin(),c.end());
}
}
dp[tmp]=(double)(ret+1)*(double)(n*(n-1)/2)/((double)(n*(n-1)/2)-ts); //转移
return;
}
dfs(need,tcnt,tsum,now+1); //不选
tmp.push_back(now);
dfs(need,tcnt+1,tsum+now,now); //继续选
dfs(need,tcnt+1,tsum+now,now+1); //选下一个
tmp.pop_back();
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
inti();
for(int i=0;i<m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
int dx=findfa(x);
int dy=findfa(y);
if(dx!=dy)
f[dx]=dy;
}
cnt=0;
vector<int> tv,tmp;
tv.push_back(n);
dp[tv]=0.0;
tv.clear();
for(int i=1;i<=n;i++)
{
int dx=findfa(i);
use[dx]++;
}
for(int i=1;i<=n;i++)
{
if(use[i]!=0)
{
tv.push_back(use[i]);
cnt++;
}
}
for(int i=2;i<=cnt;i++) //枚举联通块个数
{
ret=0.0;
tmp.clear();
dfs(i,0,0,1); //生成vector状态
}
sort(tv.begin(),tv.end());
printf("%.6f\n",dp[tv]);
}
return 0;
}