【題解】 P4284 [SHOI2014]概率充電器
阿新 • • 發佈:2019-04-16
const main cup 一個 space bits tin using inpu
\(Description:\)
一棵樹,每個點q[i]的概率直接充電,每條邊p[i]的概率導電,電可以沿邊傳遞使其他點間接充電。求進入充電狀態的點期望個數。
\(Sample\) \(Iuput:\)
3
1 2 50
1 3 50
50 0 0
\(Sample\) \(Input:\)
5
1 2 90
1 3 80
1 4 70
1 5 60
100 10 20 30 40
\(Solution:\)
考慮樹形dp,首先我們需要明白,其實我們要求的期望就是概率之和,那麽我們只要求出每個點的概率,那麽怎麽求每個點的概率捏?
yzc提供了一種神奇的辦法,考慮對於每個點,先求出每個點的子樹的概率和 \(f[u]\)
那麽對於一個點 \(u\),就只要讓 \(u\) 的所有兒子到他的概率取個並集,
概率並的求法:
\(P(A \cup B)=P(A)+P(B)-P(A)*P(B)\)
第一遍 \(dfs\) 求出所有的 \(f\),但這樣只是求出了當前這個點的子樹給他充電的概率,還沒有求父親給他充電的概率。
那麽再紀錄一個 \(g\),數組記錄當前的總概率,然後考慮 \(g\) 的轉移:
那麽我們考慮已經求出了他父親的 \(g[fa]\) 要求 \(g[v]\)
那麽就只要把 當前的點的子樹對 \(g[fa]\) 的貢獻給除掉就可以了。
求概率補集:
\(P(A)=\frac{P(A \cup B)-P(B)}{1-P(B)}\)
\((註意特判1-P(B)==0)\)
那麽就可以愉快的用兩次 \(dfs\) 求出答案。
#include<bits/stdc++.h> using namespace std; typedef double DD; int n,En; DD ans; const int N=5e5+5; inline DD unit(DD a,DD b){ return a+b-a*b; } inline DD split(DD a,DD b){ if(fabs(1.0-b)==0) return 0; return (a-b)/(1.0-b); } DD f[N],g[N],p[N]; int head[N]; struct edge{ int next,to; DD dis; }E[N<<1]; inline void add(int from,int to,DD dis){ E[++En].next=head[from]; E[En].to=to; E[En].dis=dis; head[from]=En; } inline void dfs1(int u,int fa){ f[u]=p[u]; for(int i=head[u];i;i=E[i].next){ int v=E[i].to; if(v==fa) continue; dfs1(v,u); f[u]=unit(f[u],f[v]*E[i].dis); } } inline void dfs2(int u,int fa){ for(int i=head[u];i;i=E[i].next){ int v=E[i].to; if(v==fa) continue; g[v]=unit(f[v],split(g[u],f[v]*E[i].dis)*E[i].dis); dfs2(v,u); } } int main(){ scanf("%d",&n); for(int i=1;i<=n-1;++i){ int u=0,v=0,w=0; scanf("%d%d%d",&u,&v,&w); add(u,v,(DD)w/100); add(v,u,(DD)w/100); } for(int i=1;i<=n;++i){ int x=0; scanf("%d",&x); p[i]=(DD)x/100; } dfs1(1,1); g[1]=f[1]; dfs2(1,1); for(int i=1;i<=n;++i) ans+=g[i]; printf("%.6lf\n",ans); return 0; }
【題解】 P4284 [SHOI2014]概率充電器