1. 程式人生 > >【[SHOI2014]概率充電器】

【[SHOI2014]概率充電器】

這是一道概率+樹形\(dp\)

首先我們看到這裡每一個的貢獻都是1,所以我們要求的期望就是概率

求得其實就是這個

\[\sum_{i=1}^nP_i\]

\(P_i\)為節點\(i\)通電的概率

顯然節點\(i\)通電有三種可能

  1. 它自己來電了

  2. 它的子樹裡有一個點來電了傳了過來

  3. 它的子樹外面有一個點來電了傳了過來

第一種情況最好考慮了,至於第二種和第三種我們好像很難解決的樣子

但是這顯然也告訴了我們這是一個套路題,第二種和第三種正好就是樹規裡的\(up\) \(and\) \(down\)思想

於是我們設\(h[i]\)表示第\(i\)個節點通電的概率,之後我們利用\(up\)

\(and\) \(down\)思想,在第一遍dfs的過程中,\(h[i]\)表示\(i\)通電的概率,且電一定來自它自己或者它的子樹裡(對應第一第二種情況),在第二遍dfs的時候被更新成為電來自於任何地方的概率(對應所有情況)

最開始初始化,\(h[i]=a[i]*0.01\)電只能來自自己

之後第一遍dfs,樹形dp裡的\(up\),我們要將子樹的資訊合併給根,由於根通電還是有兩種可能

  1. 根自己來電了

  2. 兒子來電,兒子通向根的邊導電

顯然這兩種情況只需要滿足一種就夠了

但是合併之後的概率是多少呢,直接加起來顯然是不對的而我還真加了起來

我們考慮有兩個事件\(A,B\),發生的概率分別是\(P(A),P(B)\)

,那麼至少發生一件的概率應該是

\[P(A)+P(B)-P(A)*P(B)\]

這個怎麼推出來的,很簡單,至少發生一件,那麼就有三種可能

  1. \(A\)發生\(B\)不發生,那麼則為\(P(A)*(1-P(B))\)

  2. \(B\)發生\(A\)不發生,那麼則為\(P(B)*(1-P(A))\)

  3. \(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;
}