【[SHOI2014]概率充電器】
這是一道概率+樹形\(dp\)
首先我們看到這裡每一個的貢獻都是1,所以我們要求的期望就是概率
求得其實就是這個
\[\sum_{i=1}^nP_i\]
\(P_i\)為節點\(i\)通電的概率
顯然節點\(i\)通電有三種可能
它自己來電了
它的子樹裡有一個點來電了傳了過來
它的子樹外面有一個點來電了傳了過來
第一種情況最好考慮了,至於第二種和第三種我們好像很難解決的樣子
但是這顯然也告訴了我們這是一個套路題,第二種和第三種正好就是樹規裡的\(up\) \(and\) \(down\)思想
於是我們設\(h[i]\)表示第\(i\)個節點通電的概率,之後我們利用\(up\)
最開始初始化,\(h[i]=a[i]*0.01\)電只能來自自己
之後第一遍dfs,樹形dp裡的\(up\),我們要將子樹的資訊合併給根,由於根通電還是有兩種可能
根自己來電了
兒子來電,兒子通向根的邊導電
顯然這兩種情況只需要滿足一種就夠了
但是合併之後的概率是多少呢,直接加起來顯然是不對的而我還真加了起來
我們考慮有兩個事件\(A,B\),發生的概率分別是\(P(A),P(B)\)
\[P(A)+P(B)-P(A)*P(B)\]
這個怎麼推出來的,很簡單,至少發生一件,那麼就有三種可能
\(A\)發生\(B\)不發生,那麼則為\(P(A)*(1-P(B))\)
\(B\)發生\(A\)不發生,那麼則為\(P(B)*(1-P(A))\)
\(A,B\)一起發生,那麼則為\(P(A)*P(B)\)
三項合起來最後一化就是\(P(A)+P(B)-P(A)*P(B)\)
所以我們合併根和子樹的資訊的時候,\(P(A)=h[i],P(B)=h[j]*p(i,j)\),\(i\)是子樹的根,\(j\)是\(i\)的兒子,\(p(i,j)\)
所以\(h[i]=P(A)+P(B)-P(A)*P(B)\)
之後我們就要考慮\(down\)了,一個節點有點也有可能來自它的父親,於是我們採用\(down\)的思想用父親更新兒子
顯然我們更新一位父親的某個兒子,顯然我們只能用其他點來電傳到父親的概率來更新這個兒子
於是我們設\(P(B)=h[j]*p(i,j)\),而且有
\[P(A)+P(B)-P(A)*P(B)=h[i]\]
我們要求的是\(P(A)\)即除了\(j\)這棵子樹其他點來電使得\(i\)有電的概率
於是解一下這個方程
\[P(A)-P(A)*P(B)=h[i]-P(B)\]
\[P(A)*(1-P(B))=h[i]-P(B)\]
\[P(A)=\frac{h[i]-P(B)}{1-P(B)}\]
而之後我們去更新兒子的話還有一邊是否導電需要考慮,於是
\[h[j]=h[j]+(P(A)*p(i,j))-h[j]*P(A)*p(i,j)\]
之後就沒有啦,同時還有一個非常坑的地方就是如果\(P(B)=h[j]*p(i,j)=1\)
那麼除以\(1-P(B)\)肯定會出錯,由於\(h[j]\)都已經是1了,顯然沒有什麼必要去更新它了,於是可以直接跳過這一層接著往下更新就好了
#include<iostream>
#include<cstring>
#include<cstdio>
#define re register
#define maxn 500005
#define eps 1e-7
struct node
{
int v,nxt,w;
}e[maxn<<1];
int num,n,m;
int a[maxn],head[maxn],deep[maxn];
double h[maxn];
double ans;
inline int read()
{
char c=getchar();
int x=0;
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9')
x=(x<<3)+(x<<1)+c-48,c=getchar();
return x;
}
inline void add_edge(int x,int y,int z)
{
e[++num].v=y;
e[num].nxt=head[x];
e[num].w=z;
head[x]=num;
}
void dfs(int x)//up
{
for(re int i=head[x];i;i=e[i].nxt)
if(!deep[e[i].v])
{
deep[e[i].v]=deep[x]+1;
dfs(e[i].v);
double k=h[e[i].v]*double(e[i].w)/100;
h[x]=h[x]+k-h[x]*k;
}
}
inline int check(double aa,double bb)
{
if(aa+eps>bb&&aa-eps<bb) return 1;
return 0;
}
void redfs(int x)//down
{
ans+=h[x];
for(re int i=head[x];i;i=e[i].nxt)
if(deep[e[i].v]>deep[x])
{
if(check(h[e[i].v]*double(e[i].w)/100,1))
{
redfs(e[i].v);
continue;
}
double k=(h[x]-h[e[i].v]*double(e[i].w)/100)/(1-h[e[i].v]*double(e[i].w)/100);
k*=double(e[i].w)/100;
h[e[i].v]=h[e[i].v]+k-k*h[e[i].v];
redfs(e[i].v);
}
}
int main()
{
n=read();
int x,y,z;
for(re int i=1;i<n;i++)
{
x=read();
y=read();
z=read();
add_edge(x,y,z),add_edge(y,x,z);
}
for(re int i=1;i<=n;i++)
a[i]=read(),h[i]=a[i]*0.01;
deep[1]=1;
dfs(1);
redfs(1);
printf("%.6lf",ans);
return 0;
}