【bzoj3566】[SHOI2014]概率充电器 树形概率dp
题目描述
著名的电子产品品牌 SHOI 刚刚发布了引领世界潮流的下一代电子产品——概率充电器:
“采用全新纳米级加工技术,实现元件与导线能否通电完全由真随机数决定!SHOI 概率充电器,您生活不可或缺的必需品!能充上电吗?现在就试试看吧!”
SHOI 概率充电器由 n-1 条导线连通了 n 个充电元件。进行充电时,每条导线是否可以导电以概率决定,每一个充电元件自身是否直接进行充电也由概率决定。
随后电能可以从直接充电的元件经过通电的导线使得其他充电元件进行间接充电。
作为 SHOI 公司的忠实客户,你无法抑制自己购买 SHOI 产品的冲动。在排了一个星期的长队之后终于入手了最新型号的 SHOI 概率充电器。
你迫不及待地将 SHOI 概率充电器插入电源——这时你突然想知道,进入充电状态的元件个数的期望是多少呢?
输入
第一行一个整数:n。概率充电器的充电元件个数。充电元件由 1-n 编号。
之后的 n-1 行每行三个整数 a, b, p,描述了一根导线连接了编号为 a 和 b 的充电元件,通电概率为 p%。
第 n+2 行 n 个整数:qi。表示 i 号元件直接充电的概率为 qi%。
输出
输出一行一个实数,为进入充电状态的元件个数的期望,四舍五入到六位小数
样例输入
3
1 2 50
1 3 50
50 0 0
样例输出
1.000000
题解
树形概率dp
先自下至上dp,求出每个子树中根节点不能工作的概率$f[x]$。其中工作需要子节点字数能工作且边存在。
然后自上至下dp,更新每个点能工作的概率$g[x]$,计算出父树的贡献,方法同理。
具体的dp方程:
$f[x]=(1-w[x])*\prod(1-f[to[i]]*val[i])$
$g[1]=f[1]$
$g[to[i]]=f[to[i]]*(1-(1-\frac{g[x]}{1-(1-f[to[i]])*val[i]})*val[i])$。
注意可能产生的除0的情况,此时$g[x]$必然等于0,特判一下就好了。
最后的答案即为$\sum\limits_{i=1}^ng[i]$。
#include <cstdio> #define N 500010 const double eps = 1e-7; int head[N] , to[N << 1] , next[N << 1] , cnt; double val[N << 1] , w[N] , f[N] , g[N]; void add(int x , int y , double z) { to[++cnt] = y , val[cnt] = z , next[cnt] = head[x] , head[x] = cnt; } void dfs1(int x , int fa) { int i; f[x] = 1 - w[x]; for(i = head[x] ; i ; i = next[i]) if(to[i] != fa) dfs1(to[i] , x) , f[x] *= 1 - (1 - f[to[i]]) * val[i]; } void dfs2(int x , int fa) { int i; for(i = head[x] ; i ; i = next[i]) { if(to[i] != fa) { if(1 - (1 - f[to[i]]) * val[i] < eps) g[to[i]] = f[to[i]] * val[i]; else g[to[i]] = f[to[i]] * (1 - (1 - g[x] / (1 - (1 - f[to[i]]) * val[i])) * val[i]); dfs2(to[i] , x); } } } int main() { int n , i , x , y; double z , ans = 0; scanf("%d" , &n); for(i = 1 ; i < n ; i ++ ) scanf("%d%d%lf" , &x , &y , &z) , add(x , y , z / 100) , add(y , x , z / 100); for(i = 1 ; i <= n ; i ++ ) scanf("%lf" , &w[i]) , w[i] /= 100; dfs1(1 , 0) , g[1] = f[1] , dfs2(1 , 0); for(i = 1 ; i <= n ; i ++ ) ans += 1 - g[i]; printf("%.6lf\n" , ans); return 0; }