可以发现一个状态跟联通块的形态是无关的,只跟联通块的大小有关
每次可以加一条边,可能会使两个联通块联通,也可能什么都不会改变。
记忆化搜索一下
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <map>
#define fi first
#define se second
using namespace std;
typedef unsigned long long ll;
typedef pair<ll,ll> PAR;
const int N=35;
const PAR P=PAR(33,37),MOD=PAR(1e9+7,1e9+9);
PAR operator +(const PAR &a,const int &b){ return PAR(a.fi+b,a.se+b); }
PAR operator +(const PAR &a,const PAR &b){ return PAR(a.fi+b.fi,a.se+b.se); }
PAR operator *(const PAR &a,const PAR &b){ return PAR(a.fi*b.fi,a.se*b.se); }
PAR operator %(const PAR &a,const PAR &b){ return PAR(a.fi%b.fi,a.se%b.se); }
int n,m,ans,tot,cnt,f[N];
int vis[N];
double C[N][N];
map<PAR,double> S;
struct STATUS{
int size,a[N];
PAR val;
void fix(){
sort(a+1,a+1+size);
val=PAR(0,0);
for(int i=1;i<=size;i++)
val=(val*P+a[i])%MOD;
}
};
int find(int x){
return x==f[x]?x:f[x]=find(f[x]);
}
double dfs(STATUS x){
if(S.count(x.val)) return S[x.val];
if(x.size==1) return 0;
double t=0,ret;
for(int i=1;i<=x.size;i++)
t+=C[x.a[i]][2];
t=1.0/(1-t/C[n][2]); ret=t;
for(int i=1;i<=x.size;i++)
for(int j=i+1;j<=x.size;j++){
STATUS cur=x;
double ct=x.a[i]*x.a[j]/C[n][2];
cur.a[i]+=cur.a[j]; cur.a[j]=cur.a[cur.size]; cur.size--;
cur.fix();
ret+=t*ct*dfs(cur);
}
return S[x.val]=ret;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) f[i]=i;
for(int i=1,x,y;i<=m;i++)
scanf("%d%d",&x,&y),f[find(x)]=find(y);
STATUS st;
st.size=0;
for(int i=1;i<=n;i++)
if(!vis[find(i)]) st.a[++st.size]=1,vis[find(i)]=st.size;
else st.a[vis[find(i)]]++;
st.fix();
C[1][0]=C[1][1]=1;
for(int i=1;i<=n;i++) C[i][0]=1;
for(int i=2;i<=n;i++)
for(int j=1;j<=i;j++)
C[i][j]=C[i-1][j-1]+C[i-1][j];
printf("%.10lf\n",dfs(st));
return 0;
}