昨天一天都在這個題上面個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;
}