【bzoj3566】[SHOI2014]概率充電器 樹形概率dp
題目描述
著名的電子產品品牌 SHOI 剛剛發布了引領世界潮流的下一代電子產品——概率充電器:
“采用全新納米級加工技術,實現元件與導線能否通電完全由真隨機數決定!SHOI 概率充電器,您生活不可或缺的必需品!能充上電嗎?現在就試試看吧!”
SHOI 概率充電器由 n-1 條導線連通了 n 個充電元件。進行充電時,每條導線是否可以導電以概率決定,每一個充電元件自身是否直接進行充電也由概率決定。
隨後電能可以從直接充電的元件經過通電的導線使得其他充電元件進行間接充電。
作為 SHOI 公司的忠實客戶,你無法抑制自己購買 SHOI 產品的沖動。在排了一個星期的長隊之後終於入手了最新型號的 SHOI 概率充電器。
輸入
第一行一個整數:n。概率充電器的充電元件個數。充電元件由 1-n 編號。
之後的 n-1 行每行三個整數 a, b, p,描述了一根導線連接了編號為 a 和 b 的充電元件,通電概率為 p%。
第 n+2 行 n 個整數:qi。表示 i 號元件直接充電的概率為 qi%。
輸出
輸出一行一個實數,為進入充電狀態的元件個數的期望,四舍五入到六位小數
樣例輸入
3
1 2 50
1 3 50
50 0 0
樣例輸出
1.000000
題解
樹形概率dp
先自下至上dp,求出每個子樹中根節點不能工作的概率$f[x]$。其中工作需要子節點字數能工作且邊存在。
然後自上至下dp,更新每個點能工作的概率$g[x]$,計算出父樹的貢獻,方法同理。
具體的dp方程:
$f[x]=(1-w[x])*\prod(1-f[to[i]]*val[i])$
$g[1]=f[1]$
$g[to[i]]=f[to[i]]*(1-(1-\frac{g[x]}{1-(1-f[to[i]])*val[i]})*val[i])$。
註意可能產生的除0的情況,此時$g[x]$必然等於0,特判一下就好了。
最後的答案即為$\sum\limits_{i=1}^ng[i]$。
#include <cstdio> #define N 500010 const double eps = 1e-7; int head[N] , to[N << 1] , next[N << 1] , cnt; double val[N << 1] , w[N] , f[N] , g[N]; void add(int x , int y , double z) { to[++cnt] = y , val[cnt] = z , next[cnt] = head[x] , head[x] = cnt; } void dfs1(int x , int fa) { int i; f[x] = 1 - w[x]; for(i = head[x] ; i ; i = next[i]) if(to[i] != fa) dfs1(to[i] , x) , f[x] *= 1 - (1 - f[to[i]]) * val[i]; } void dfs2(int x , int fa) { int i; for(i = head[x] ; i ; i = next[i]) { if(to[i] != fa) { if(1 - (1 - f[to[i]]) * val[i] < eps) g[to[i]] = f[to[i]] * val[i]; else g[to[i]] = f[to[i]] * (1 - (1 - g[x] / (1 - (1 - f[to[i]]) * val[i])) * val[i]); dfs2(to[i] , x); } } } int main() { int n , i , x , y; double z , ans = 0; scanf("%d" , &n); for(i = 1 ; i < n ; i ++ ) scanf("%d%d%lf" , &x , &y , &z) , add(x , y , z / 100) , add(y , x , z / 100); for(i = 1 ; i <= n ; i ++ ) scanf("%lf" , &w[i]) , w[i] /= 100; dfs1(1 , 0) , g[1] = f[1] , dfs2(1 , 0); for(i = 1 ; i <= n ; i ++ ) ans += 1 - g[i]; printf("%.6lf\n" , ans); return 0; }
【bzoj3566】[SHOI2014]概率充電器 樹形概率dp