【描述】
著名的电子产品品牌 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,所以答案就是所有点充电的概率和。所以我们就可以考虑树形dp了。显然,由于父节点对子节点有贡献,所以这是一道换根dp。我们首先定义
f
[
u
]
f[u]
f
[
u
]
表示点u自己充电或子树使它充电的概率。注意,这是两件独立的事件A,B,我们要求的是至少发生一件的概率。通过简单的容斥易得我们要求的是:
P
(
A
)
+
P
(
B
)
−
P
(
A
)
∗
P
(
B
)
P(A)+P(B)-P(A)*P(B)
P
(
A
)
+
P
(
B
)
−
P
(
A
)
∗
P
(
B
)
。即
f
[
u
]
=
∑
(
p
[
u
]
+
p
(
e
(
u
−
>
v
)
)
∗
f
[
v
]
−
p
(
e
(
u
−
>
v
)
)
∗
f
[
v
]
∗
p
[
u
]
)
f[u]=\sum (p[u]+p(e(u->v))*f[v]-p(e(u->v))*f[v]*p[u])
f
[
u
]
=
∑
(
p
[
u
]
+
p
(
e
(
u
−
>
v
)
)
∗
f
[
v
]
−
p
(
e
(
u
−
>
v
)
)
∗
f
[
v
]
∗
p
[
u
]
)
。于是第一次dp就OK啦。现在,除了根节点的f是正确的,剩下的点均未考虑父亲的贡献。此时我们假设父亲的f已更新为最终的f,现在考虑父亲发电通过边传给儿子,似乎挺好算的。此时,儿子由自己和子树发电,与父亲使儿子发电,又是两个独立的事件,我们同样可以套用上面的容斥进行更新。这就完了?不不不。当你发现过不了样例的时候,才发现,我们计算的父亲的f里包含了这个儿子使它发电的概率(父亲的电是儿子给的,儿子的电是父亲给的,那电…?)。所以我们需要去掉这一部分贡献。可是我们又遇到了困难,我们得到了如下式子:
f
[
v
]
+
=
w
∗
(
f
[
u
]
−
w
∗
f
[
v
]
∗
(
1
−
f
′
[
u
]
)
)
∗
(
1
−
f
[
v
]
)
f[v]+=w*(f[u]-w*f[v]*(1-f'[u]))*(1-f[v])
f
[
v
]
+
=
w
∗
(
f
[
u
]
−
w
∗
f
[
v
]
∗
(
1
−
f
′
[
u
]
)
)
∗
(
1
−
f
[
v
]
)
其中
f
′
[
u
]
f'[u]
f
′
[
u
]
是
f
[
u
]
f[u]
f
[
u
]
除去v及其子树的贡献的
f
[
u
]
f[u]
f
[
u
]
。计算
f
′
[
u
]
f'[u]
f
′
[
u
]
需要
f
′
[
u
]
f'[u]
f
′
[
u
]
?怎么办?然后我就干了一件蠢事…迭代…当我把我可爱的1.21kb代码通过迭代变成一份11.67kb代码时,
我就获得了90分的好成绩
,我终于向精度低头了,误差依然存在(或者说long double爆了)。怎么办?我这才想起来有一种操作叫作移项…我们知道:
f
′
[
u
]
=
f
[
u
]
−
w
∗
f
[
v
]
∗
(
1
−
f
′
[
u
]
)
f'[u]=f[u]-w*f[v]*(1-f'[u])
f
′
[
u
]
=
f
[
u
]
−
w
∗
f
[
v
]
∗
(
1
−
f
′
[
u
]
)
f
′
[
u
]
=
(
f
[
u
]
−
w
∗
f
[
v
]
)
/
(
1
−
w
∗
f
[
v
]
)
f'[u]=(f[u]-w*f[v])/(1-w*f[v])
f
′
[
u
]
=
(
f
[
u
]
−
w
∗
f
[
v
]
)
/
(
1
−
w
∗
f
[
v
]
)
于是带入第二次的转移方程即可。注意判断w*f[v]==1的情况。
代码(
优美而简洁,和压行没有关系
):
#include<bits/stdc++.h>
#define re register
#define mp make_pair
using namespace std;
const int N=5e5+5;
inline int red(){
int data=0;bool w=0; char ch=0;
ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return w?-data:data;
}
int n,m,a,b;double c;
vector<pair<int,double> >g[N];
double f[N],p[N],h[N];
void dfs1(int u,int fa){f[u]=p[u];
for(int re i=g[u].size()-1;~i;--i){
int v=g[u][i].first;
double w=g[u][i].second;
if(v==fa)continue;dfs1(v,u);
f[u]+=(1.0-f[u])*w*f[v];
}
}
double ans=0;
void dfs2(int u,int fa){ans+=f[u];
for(int re i=g[u].size()-1;~i;--i){
int v=g[u][i].first;
double w=g[u][i].second;
if(v==fa)continue;
if(f[v]*w!=1.0)f[v]+=w*(f[u]-f[v]*w)/(1-w*f[v])*(1.0-f[v]);
dfs2(v,u);
}
}
int main(){
n=red();
for(int re i=1;i^n;i++){
a=red();b=red();c=red()*0.01;
g[a].push_back(mp(b,c));
g[b].push_back(mp(a,c));
}for(int re i=1;i<=n;i++)p[i]=red()*0.01;
dfs1(1,0);dfs2(1,0);printf("%.6f\n",(double)(ans+1e-7));
}