[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 \times=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\n",ans); 35 return 0; 36 }