題目鏈接:http://www.lydsy.com/JudgeOnline/problem.php?id=3925
題意概述:
給出一張N點M邊的最小生成樹,其中每條邊的長度爲[0,1]的實數,求最小生成樹中最大邊的期望大小是多少。
N<=10,M<=N(N-1)/2.
由於本人實在太弱所以細節會描述的比較多。(精簡版本見下方)
整理一下手裏有的信息:
1.題目告訴我們N個大小在[0,1]的隨機變量中第i大的期望大小爲i/(N+1)。
2.由於兩個[0,1]的隨機變量大小相同的概率爲0(小到爲0),所以說只考慮每個隨機變量大小不同的情況就可以了。
3.在所有邊不同的條件下,在做最小生成樹的時候第k條考察的邊就是所有邊中第k小的邊(kruskal算法)。
所以如果知道圖中最後一條加入最小生成樹的邊爲所有邊中第i大的概率p[i],那麼答案就可以表示爲:
ans=sum{ p[i] * i/(M+1) | 1<=i<=M }
根據信息3,可以發現p[i]實際上也就是第i條考察的邊加入後恰好使得圖連通的概率(注意i不是按照給出的邊的順序來考察的)。這又轉化爲了一個圖的連通性問題,只要我們知道一個圖中選擇i條邊使得圖連通的概率xi,那麼p[i]=xi-xi-1就是在第i條邊加入之後圖恰好連通的概率(因爲i-1->i有兩種可能,i-1就是連通的或者i-1是不連通的,所以是減法)。
好像某人說過“概率說到底還是一個組合計數問題”(離散概率),我們把這個概率xi用方案數來表示(注意到M<=45所以用C估計一下算出來的方案數的範圍,並沒有毛病)。設g(i)表示用i條邊將圖連通的方案數,則xi=g(i)/C(M,i)。(i條邊使圖連通的概率=i條邊令圖連通的方案數/選出i條邊的方案數)
於是ans=sum{ (g(i)/C(M,i) - g(i-1)/C(M,i-1)) * i/(M+1) | 1<=i<=M }
接下來求出g問題就解決了。我們設g(s,i)表示用i條邊將點集s中的點連通的方案數,那麼f(s,i)表示用i條邊不能將點集s中的點連通的方案數,有g(s,i)=C(cnt[s],i)-f(s,i),cnt[x]表示兩端都在點集x中的邊的條數。對於每個f(s,i)任意選擇s中的一個點x,f(s,i)=sum{ g(ss,ii)*C(cnt[s-ss],i-ii) | ss爲s的真子集且包含x,sz[ss]-1<=ii<=min(i,cnt[ss]),i-ii<=cnt[s-ss] },sz[s]表示集合s中點的數量。(方程的正確性:對於任意一種不連通的情況,一定存在至少兩個連通分量,找到一個連通分量並枚舉其所有連通的情況,用其方案數乘上對應情況下其他所有點可能的形態方案數累加到答案中)
於是再整理一下:ans=sum{ (g(2N-1,i)/C(M,i) - g(2N-1,i-1)/C(M,i-1)) * i/(M+1) | 1<=i<=M }
實際上到這裏就完了但是敏銳的人發現方程中的g換成f之後好像可以化簡?!由於原圖是連通的所以當選用M條邊的時候f(2N-1,M)=0,這樣可以把展開後的式子的最後一項剩餘的消掉,最後答案寫爲:ans=sum{ f(2N-1,i)/C(M,i) | 1<=i< M } / (M+1)
是不是很順理成章?但是真的很抱歉自己想我真的。。。。。。ORZ
精簡版本:
g(s,i)表示用i條邊將點集s中的點連通的方案數,f(s,i)表示用i條邊不能將點集s中的點連通的方案數,有g(s,i)=C(cnt[s],i)-f(s,i),cnt[x]表示兩端都在點集x中的邊的條數。
令x爲s中任意一個點,f(s,i)=sum{ g(ss,ii)*C(cnt[s-ss],i-ii) | ss爲s的真子集且包含x,sz[ss]-1<=ii<=min(i,cnt[ss]),i-ii<=cnt[s-ss] },sz[s]表示集合s中點的數量。
ans = sum{ (g(2N-1,i)/C(M,i) - g(2N-1,i-1)/C(M,i-1)) * i/(M+1) | 1<=i<=M } = sum{ f(2N-1,i)/C(M,i) | 1<=i< M } / (M+1)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<cctype>
using namespace std;
const int maxn=12;
const int maxm=50;
const int maxs=(1<<10)+5;
typedef long long LL;
int N,M;
int bin[maxn],cnt[maxs],sz[maxs];
LL f[maxs][maxm],g[maxs][maxm],C[maxm][maxm];
struct edge{ int u,v; }E[maxm];
void data_in()
{
scanf("%d%d",&N,&M);
int x,y;
for(int i=1;i<=M;i++){
scanf("%d%d",&x,&y);
E[i]=(edge){x,y};
}
}
void dp()
{
for(int i=1;i<=N+1;i++) bin[i]=1<<i-1;
for(int i=0;i<=M;i++){
C[i][0]=1;
for(int j=1;j<=i;j++)
C[i][j]=C[i-1][j]+C[i-1][j-1];
}
for(int s=1;s<bin[N+1];s++)
for(int i=1;i<=M;i++)
if((bin[E[i].u]&s)&&(bin[E[i].v]&s)) cnt[s]++;
for(int s=1;s<bin[N+1];s++){
sz[s]=sz[s>>1]+(s&1);
for(int i=0;i<=cnt[s];i++){
if(sz[s]==1) g[s][i]=1;
else{
int x;
for(x=1;x<=N;x++) if(bin[x]&s) break;
for(int ss=(s-1)&s;ss;ss=(ss-1)&s) if(bin[x]&ss)
for(int ii=sz[ss]-1;ii<=min(i,cnt[ss]);ii++)
if(i-ii<=cnt[s^ss])
f[s][i]+=g[ss][ii]*C[cnt[s^ss]][i-ii];
g[s][i]=C[cnt[s]][i]-f[s][i];
}
}
}
}
void work()
{
dp();
double ans=0;
int all=bin[N+1]-1;
for(int i=0;i<M;i++)
ans+=1.0*f[all][i]/C[cnt[all]][i];
printf("%.6f\n",ans/(M+1));
}
int main()
{
data_in();
work();
return 0;
}