洛谷传送门
题目描述
著名的电子产品品牌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%。
输出格式:
输出一行一个实数,为能进入充电状态的元件个数的期望,四舍五入到小 数点后6 位小数。
输入输出样例
输入样例#1:
3
1 2 50
1 3 50
50 0 0
输出样例#1:
1.000000
输入样例#2:
5
1 2 90
1 3 80
1 4 70
1 5 60
100 10 20 30 40
输出样例#2:
4.300000
说明
对于30%的数据, n≤5000 n ≤ 5000 。
对于100%的数据, n≤500000 n ≤ 500000 , 0≤p,qi≤100 0 ≤ p , q i ≤ 100 。
结题分析
一眼就可以看出是树上概率DP题, 但可以发现如果我们计算每个原件通电的概率, 它们是否通电的概率是会受自己、 儿子和爸爸元件,以及连接的它们导线的通电概率的影响, 而这些情况是取或( | )的, 如此讨论一定会很麻烦, 容易算重或遗漏。
正向考虑很繁琐, 那我们可以反向考虑, 即每个元件不通电的概率。 我们发现这样算的话所有情况是取并( & )的, 即自己、 儿子和爸爸都不通电或导线不通电, 这样就更方便计算。
因为所有的导线、 元件构成了一颗树, 那么我们现在从两个方向来考虑不通电的可能。
1.子树方向没有电传过来(包括自己没有电)。 设 f(x) f ( x ) 为点 x x 子树方向没有电传过来的概率, 为原件x或导线x没有电的概率, 那么有如下公式:
(因为同时都要满足, 所以概率应该相乘)
对于树上的叶节点x, 显然
f(x)=p(x)
f
(
x
)
=
p
(
x
)
, 所以我们可以从树根DFS递归下去,得到所有的
f(x)
f
(
x
)
。
2.父亲节点方向没有电传过来。这种情况下我们假设 x x 子树方向没有电(因为上一种情况我们已经将这些情况考虑了)。设 表示非根节点 x x 父亲方向没有电传过来的概率, 表示 x x 的父节点,表示父节点的父节点,父节点没有电的概率为 Pfat P f a t 有如下公式:
因为我们已经假设x没有电了, 所以我们要消除 x x 子树方向对的影响, 故将 x x 子树方向的概率除掉。显然在这里因为根节点没有父亲, 所以 。再结合如下公式:
即可从树根一次DFS递推得到所有的 g(x) g ( x ) 。而根据题意所求为:
就可以A掉本题辣!
代码如下:
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#define R register
#define gc getchar()
#define IN inline
#define W while
#define MX 500005
#define db double
#define EPS 1e-8
int head[MX], cnt, fat[MX];
db f[MX], h[MX], g[MX];
struct Edge
{
int to, nex;
db val;
}edge[MX << 1];
IN void addedge(const int &from, const int &to, const db &val)
{
edge[++cnt] = (Edge){to, head[from], val};
head[from] = cnt;
}
void DFS1(const int &now, const int &fa)
{//求出f(x)
fat[now] = fa;
for (R int i = head[now]; i; i = edge[i].nex)
{
if(edge[i].to == fa) continue;
DFS1(edge[i].to, now);//递归到最底层叶节点再回溯计算
h[edge[i].to] = f[edge[i].to] + (1 - f[edge[i].to]) * (1.0 - edge[i].val);//存入中间变量h[],方便后面计算。
f[now] *= h[edge[i].to];
}
}
void DFS2(const int &now , const int &fa)
{
for (R int i = head[now]; i; i = edge[i].nex)
{
if(edge[i].to == fa) continue;
db t = h[edge[i].to] < EPS ? 0 : g[now] * f[now] / h[edge[i].to];
g[edge[i].to] = t + (1.0 - t) * (1.0 - edge[i].val);
DFS2(edge[i].to, now);
}
}
int main()
{
int dot, a, b, c;
scanf("%d", &dot);
for (R int i = 1; i < dot; ++i)
{
scanf("%d%d%d", &a, &b, &c);
addedge(a, b, c / 100.0), addedge(b, a, c / 100.0);
}
for (R int i = 1; i <= dot; ++i)
{
scanf("%d", &a);
f[i] = 1.0 - a / 100.0;
}
DFS1(1, 0);
g[1] = 1.0;
DFS2(1, 0);
db ans = 0.0;
for (R int i = 1; i <= dot; ++i) ans += 1.0 - g[i] * f[i];
printf("%.6lf", ans);
return 0;
}