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

输出一个小数,表示新修道路条数的期望值,保留六位小数.
Sample Input
4 2

1 2

3 4

Sample Output
1.500000

 给出N个点M条边,每次等概率的给这张图加一条边,求连通这个图加边次数的期望。
 
这题我们并不关心图的形状,只要知道图的连通情况即可,即这张图有几个连通块,每个连通块中有几个顶点。比

如,我们用E(2,3,3)表示当这个图有3个连通块,每个连通块中的顶点数分别是2,3,3时,我们把它连通所需加边的期望,

对于8个顶点的图,E(8)=0。可以得到转移方程

E(2,3,3)=p1*E(2,3,3)+p2*E(2,6)+p3*E(3,5)+1。

把右边的第一项移到左边,然后用记忆化搜索就可以求解了。

其中p1,p2,p3是转移到其它连通情况的概率,在8个点里连一条边,总的方案数显然是C(8,2)。如果这条边没有改变连

通情况,说明连的是同一个连通块里的边,于是p1=(C(2,2)+C(3,2)+C(3,2))/C(8,2)。否则就是在两个连通块里各选了一

条边,枚举可以得到p2=(3*3)/C(8,2),p3=(2*3+2*3)/C(8,2)。

对图的每种状态先排个序,然后用HASH即可。

///BZOJ 1417

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
#define ll long long
#define vec vector<int>
const int maxn = 100;

map<vec, double>mp;
vec ve;
int fa[maxn], siz[maxn], all, n, m;
int find_set(int x){
    if(x==fa[x]) return x;
    else return fa[x]=find_set(fa[x]);
}
void init(){
    scanf("%d%d", &n,&m);
    for(int i=1; i<=n; i++) fa[i]=i,siz[i]=1;
    for(int i=1; i<=m; i++){
        int u,v;
        scanf("%d%d",&u,&v);
        int fx = find_set(u), fy = find_set(v);
        if(fx!=fy) fa[fx]=fy,siz[fy]+=siz[fx];
    }
    for(int i=1; i<=n; i++) if(i==fa[i]) ve.push_back(siz[i]);
    sort(ve.begin(),ve.end());
    all = n*(n-1)/2;
}

double DP(vec ve)
{
    if(mp.count(ve)) return mp[ve];
    if(ve.size()==1) return mp[ve]=0;
    int sz=ve.size();
    int p=0;
    for(int i=0; i<sz; i++){
        p+=ve[i]*(ve[i]-1)/2;
    }
    double ans=1.0*all/(all-p);
    for(int i=0; i<sz; i++){
        for(int j=0; j<i; j++){
            vec v=ve;
            v[j]+=v[i];
            swap(v[i], v[sz-1]);
            v.pop_back();
            sort(v.begin(),v.end());
            ans+=1.0*ve[i]*ve[j]/(all-p)*DP(v);
        }
    }
    return mp[ve]=ans;
}

int main()
{
    init();
    printf("%.6f\n", DP(ve));
    return 0;
}