1. 程式人生 > >【BZOJ 3925】【ZJOI 2015】[概率dp]地震後的幻想鄉

【BZOJ 3925】【ZJOI 2015】[概率dp]地震後的幻想鄉

題目描述

題目分析

PoPoQQQ大爺的概率DP看不懂,看了另外一個大神的題解…好像跟概率dp沒什麼關係。
根據提示,對於n個[0,1]之間的隨機變數x1,x2,...,xm,第k小的那個的期望值是km+1,那麼,我們不妨計算出在整個圖中剛好選擇k條邊使得該圖聯通的概率,乘以它的貢獻km+1,因為若算出選擇k條邊而使其聯通的概率並不能保證其剛好是k條邊,於是,我們不妨處理出Ps,i表示對於點集s,選擇i條邊而不連通的概率,但是我們要求的是選擇i條邊剛好連通的概率,很明顯就是1Ps,i)(1Ps,i1),所以Ans=1m+1×(Pall,0Pall,1)+2m+1×(Pal

l,1Pall,2)+...+m+1m+1×(Pall,m)
P提出來就有Ans=Σmi=0Pall,im+1
現在的任務是怎麼計算P,我們可以先把不連通的方案數算出來再除以總方案數,設不連通的方案數為fs,i,連通的方案數為gs,i。就有fs,i+gs,i=Ccnts,icntss形成的子圖中的邊數)
對於計算fs,我們不妨隨便找一個定點,而列舉定點所在s的真子集,在累加一下方案數就可以求出fsfs,i+j=gt,iCcntst,j|i[0,cnts],j[0,cntst],因為沒有列舉sst中的邊,所以說sst必然不連通。
具體細節可以參考程式碼。

程式碼

/**************************************************************
    Problem: 3925
    User: bzjudge2
    Language: C++
    Result: Accepted
    Time:104 ms
    Memory:2384 kb
****************************************************************/

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring> #include<cmath> using namespace std; #define MAXN 10 #define MAXM 50 #define MAXS 1024 #define INF 0x3f3f3f3f typedef long long int LL; template<class T> void Read(T &x){ x=0;char c=getchar();bool flag=0; while(c<'0'||'9'<c){if(flag)x=-x;c=getchar();} while('0'<=c&&c<='9'){x=x*10+c-'0';c=getchar();} if(flag)x=-x; } int n,m; int to[MAXN+10]; int cnt[MAXS+100],sz[MAXS+100]; LL f[MAXS+100][MAXM+10];//S中選j條邊使得不連通 LL g[MAXS+100][MAXM+10];//S中選j條邊使得連通 LL C[MAXM+10][MAXM+10]; void init(){ memset(g,0,sizeof(g)); memset(f,0,sizeof(f)); C[0][0]=1; for(int i=1;i<=MAXM;++i){ C[i][0]=C[i][i]=1; for(int j=1;j<i;++j) C[i][j]=C[i-1][j-1]+C[i-1][j]; } } int main(){ init(); Read(n),Read(m); int a,b; for(int i=1;i<=m;++i){ Read(a),Read(b); --a,--b; to[b]|=1<<a; to[a]|=1<<b; } int ed=1<<n; for(int s=1;s<ed;++s){ sz[s]=sz[s>>1]+(s&1); if(sz[s]==1){ g[s][0]=1; continue; } for(int i=0;i<n;++i) if(s&(1<<i))cnt[s]+=sz[to[i]&s];//i點可以到達的s內點的邊數 cnt[s]>>=1; int lowbit=s&-s;//任選一點 for(int t=s&(s-1);t>0;t=(t-1)&s)//列舉每一中包含lowbit的s子集 if(t&lowbit){ for(int i=0;i<=cnt[t];++i)//t中選擇的邊數 for(int j=0;j<=cnt[s^t];++j)//s-t中選擇的邊數 f[s][i+j]+=g[t][i]*C[cnt[s^t]][j];//保證不選擇s~t的邊 } for(int i=0;i<=cnt[s];++i) g[s][i]=C[cnt[s]][i]-f[s][i]; } double ans=0; for(int i=0;i<=m;++i) ans+=(double)f[ed-1][i]/C[cnt[ed-1]][i]; printf("%0.6lf\n",ans/(m+1)); }