昨天一天都在这个题上面个wa,晚上回来又重新打了一遍,然后就进400多毫秒过了这个题目。。。很是惊讶。然后仔细一看,原来是之前有地方写错了,这让我情何以堪。。。。发现自己打代码的能力越来越差,不能很快的将自己的思路用代码给表现出来。。。还要一直狂DEBUG(现在愈来愈认为DEBUG是一个非常不好的东西。。。)。。。
计算期望的时候:E(i) = pk* E(i) + sum(p1*E[j]) + 1,其中i中的连分块的个数比j中的多一个。。也就是说j是i通过合并两个连分块得到的。。。首先我们可以求出这个图的连分块的个数以及每个连分块的点的数量,直接用并查集就可以完成统计功能。。然后就采用记忆化搜索的方式进行DP。由于状态数不能确定,我们这里可以直接采用hash的方式边求边保存。。这里hash是关键,将对应的每个连分块的数目存进数组里,然后排序,在用BKDRHash.
/*
author : csuchenan
prog : POJ3156
algorithm: DP 记忆化搜索+hash
csuchenan 3156 Accepted 1216K 469MS C++ 2323B 2012-10-23 23:59:50
用BKDRhash速度快了许多。。。
*/
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
const int mod = 10007;
struct Node{
int x[31];//连通分量对应的点的个数
int xs;//连通分量的个数
double exp;
void init(){
memset(x, 0, sizeof(x));
xs = 0;
}
bool operator==(Node &a){
for(int i = 0; i != 31; i ++){
if(a.x[i] != x[i])
return false;
}
return true;
}
void sort(){
std::sort(x, x+31);
}
//BKDR hash
unsigned int hash(){
unsigned int hash = 0, seed = 131;
for(int i = 30 ; i >=0 && x[i] ; i--){
hash = hash * seed + x[i];
}
return (hash & 0x7fffffff)%mod;
}
};
std::vector<Node> hh[mod];
double search(Node &a){
unsigned int x = a.hash();
for(int i = 0; i != hh[x].size(); i ++){
if(a == hh[x][i]){
return hh[x][i].exp;
}
}
return -1;
}
int p[31], tot[31];
int n, m, tu, tv;
int find(int x){
return x == p[x] ? x : (p[x] = find(p[x]));
}
double DP(Node ost){
//当只有一个连通块的个数的时候,说明已经连成一体
if(ost.xs == 1){
return 0;
}
double x = search(ost);
if(x != -1.0)
return x;
double tmp = 0, ans = 0;
//统计添加边后不会改变现有连通性
for(int i = 0; i != 31; i ++){
tmp += ost.x[i] * (ost.x[i]-1)/2;
}
//统计添加边后,连通块的个数会减少
for(int i = 0; i != 31; i ++){
for(int j = i+1; j != 31; j ++){
if(ost.x[i]==0 || ost.x[j]==0)
continue;
Node sst = ost;
sst.x[i] = sst.x[i] + sst.x[j];
sst.x[j] = 0;
sst.xs --;
sst.sort();
ans += ost.x[i] * ost.x[j] * DP(sst);
}
}
ans = ans / (n*(n-1)/2) + 1 ;
ans = ans / (1 - tmp/(n*(n-1)/2) ) ;
ost.exp = ans;
hh[ost.hash()].push_back(ost);
return ans;
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i != 31; i ++){
p[i] = i;
tot[i] = 0;
}
//统计连通块的个数以及每个连通块的节点个数
for(int i = 0; i < m; i ++){
scanf("%d%d", &tv, &tu);
p[find(tv)] = find(tu);
}
for(int i = 1; i <= n; i ++){
tot[find(i)] ++;
}
Node st;
st.init();
for(int i = 1; i <= n; i ++){
if(tot[i]){
st.x[st.xs ++] = tot[i];
}
}
//这里一定要排序,便于后面判断两个状态相等
st.sort();
printf("%.10lf\n", DP(st));
return 0;
}