[BZOJ3566][SHOI2014]概率充电器(概率DP)

题意:树上每个点有概率有电,每条边有概率导电,求每个点能被通到电的概率。

较为套路但不好想的概率DP。

树形DP肯定先只考虑子树,自然的想法是f[i]表示i在只考虑i子树时,能有电的概率,但发现无法转移,因为只要有任何一个儿子同时满足“儿子有电且儿子到i的边导电”,这个点就能导电,而“或”命题在外层的概率通常因为容易算重而不好计算。

正难则反,考虑f[i]表示i在只考虑子树时无法通电的概率,它等于“所有儿子均不通电或儿子到这条边不导电”,转移方程是$f_x=(1-p_i)prod (1-pre_k+pre_k*f_k)$,其中k为i的儿子,pre[k]为i到x的边导电的概率。

从父亲转移到儿子的概率同理,即满足“父亲在不考虑x子树的情况下不通电或父亲到x的边不到电”的概率,注意要除去x子树对f[fa]的影响。

方程是$f_x imes=1-pre_x+frac{pre_x*f_{fa}}{1-pre_x+pre_x*f_x}$

注意判掉分母为零的情况。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 5 #define For(i,x) for (int i=h[x],k; i; i=nxt[i])
 6 using namespace std;
 7 
 8 const int N=1000010,eps=1e-7;
 9 int n,u,v,cnt,to[N],nxt[N],h[N];
10 double val[N],w,ans,p[N],f[N];
11 void add(int u,int v,double w){ to[++cnt]=v; val[cnt]=w; nxt[cnt]=h[u]; h[u]=cnt; }
12 inline double Abs(double x){ return (x<0) ? -x : x; }
13 
14 void dfs(int x,int fa){
15     f[x]=1-p[x];
16     For(i,x) if ((k=to[i])!=fa) dfs(k,x),f[x]*=(f[k]-1)*val[i]+1;
17 }
18 
19 void dfs2(int x,int fa){
20     For(i,x) if ((k=to[i])!=fa){
21         if (Abs((f[k]-1)*val[i]+1)>eps) f[k]*=1-val[i]+val[i]*f[x]/((f[k]-1)*val[i]+1);
22         dfs2(k,x);
23     }
24 }
25 
26 int main(){
27     freopen("bzoj3566.in","r",stdin);
28     freopen("bzoj3566.out","w",stdout);
29     scanf("%d",&n);
30     rep(i,2,n) scanf("%d%d%lf",&u,&v,&w),add(u,v,w/100.),add(v,u,w/100.);
31     rep(i,1,n) scanf("%lf",&p[i]),p[i]/=100.;
32     dfs(1,0); dfs2(1,0);
33     rep(i,1,n) ans+=1-f[i];
34     printf("%.6lf
",ans);
35     return 0;
36 }
原文地址:https://www.cnblogs.com/HocRiser/p/9799784.html