链接
题目描述
给出一颗无根树,每个点有一定概率充电,充上电的点可以通过边来给别的点充电,边有一定概率通电,现在要求这棵树的通电点数的期望
样例输入
3
1 2 50
1 3 50
50 0 0
样例输出
1.000000
思路
好像直接求不是很好求
那么我们把求总的点数的期望改成求一个点通电的概率,然后再加起来就是答案了
那么一个点怎么求通电概率呢
首先想到的是设
f
i
f_i
fi为i点通电的总概率,但是这样子会有很多的式子要处理,那么我们就考虑设
f
i
f_i
fi为该点不通电的概率
很容易想到转移就是
f
i
=
(
1
−
p
i
)
∗
(
1
−
g
i
,
v
+
g
i
,
v
∗
f
v
)
f_i = (1-p_i)*(1-g_{i,v}+g_{i,v}*f_ v)
fi=(1−pi)∗(1−gi,v+gi,v∗fv)
解释一下,首先是它自身不通电
(
1
−
p
i
)
(1-p_i)
(1−pi),然后是它连的边不通电
1
−
g
i
,
v
1-g_{i,v}
1−gi,v.再然后就是边通电,那么它连的点就不可以通电
(
g
i
,
v
∗
f
v
)
(g_{i,v}*f_ v)
(gi,v∗fv)
这个可以通过设1点为根去求
那么就可以得到1点的正确答案
但是,想到原图其实是一颗无根树
那么每个点都有可能为根,我们就需要考虑别的点为根的情况
很显然,一个点从儿子变为根,它会多出原本它的父亲的贡献
红色部分,在第一次以1为根时求出的,在以u为根的情况下就成为了u的贡献
那么设
g
i
g_i
gi为以i为根时的最终答案
额外贡献很显然就是它原父亲的贡献/它自己的贡献(因为是乘法)
q
=
g
f
a
(
1
−
w
f
a
,
i
+
w
f
a
,
i
∗
f
i
)
q = \dfrac{g_{fa}}{(1-w_{fa,i} + w_{fa,i}*f_i)}
q=(1−wfa,i+wfa,i∗fi)gfa
g
i
=
f
i
∗
(
1
−
w
f
a
,
i
+
w
f
a
,
i
∗
q
)
g_i = f_i * (1-w_{fa,i}+w_{fa,i} * q)
gi=fi∗(1−wfa,i+wfa,i∗q)
这个换根DP可以用两次搜索实现,第一次以1为根,第二次就不断换根
但是jzoj很恶心地用dfs会爆栈,所以我用的bfs打(很丑勿喷)
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
int n, tot, t, h[500005];
double q[500005], f[500005], g[500005], ans;
struct lq
{
int a, b, c;
}st[500005];
struct node
{
int to, next;
double val;
}w[1000005];
struct qq
{
int now, fa;
};
void bfsl(int x, int fa)
{
st[++tot] = (lq){x, fa, 0};
//第三个位置这里,0表示还未处理儿子,1表示儿子处理完
//当儿子处理完了向上转移才是正确的
while(tot)
{
int now = st[tot].a;
int father = st[tot].b;
int pd = st[tot--].c;
if(!pd)
{
st[++tot] = (lq){now, father, 1};//定为1,然后处理儿子(因为是栈,所以一定先处理后面加入的)
f[now] = 1.0 - q[now];
for(int i = h[now]; i; i = w[i].next)
{
int to = w[i].to;
if(to == father) continue;
st[++tot] = (lq){to, now, 0};
}
}
else {
for(int i = h[now]; i; i = w[i].next)
{
int to = w[i].to;
if(to == father) continue;
f[now] *= (1.0 - w[i].val + w[i].val * f[to]);
}
}
}
}
void bfsq(int x, int fa)
{
queue<qq>Q;
Q.push((qq){x, fa});
while(Q.size())
{
int now = Q.front().now;
int father = Q.front().fa;
Q.pop();
ans += g[now];
for(int i = h[now]; i; i = w[i].next)
{
int to = w[i].to;
if(to == father) continue;
double rest = g[now] / (1 - w[i].val + w[i].val * f[to]);
g[to] = f[to] * (1 - w[i].val + w[i].val * rest);
Q.push((qq){to, now});
}
}
}//换根DP
int main()
{
// freopen("charger.in", "r", stdin);
// freopen("charger.out", "w", stdout);
scanf("%d", &n);
for(int i = 1; i < n; ++i)
{
int u, v, p;
double pp;
scanf("%d%d%d", &u, &v, &p);
pp = 1.0 * p / 100.0;
w[++t] = (node){v, h[u], pp}; h[u] = t;
w[++t] = (node){u, h[v], pp}; h[v] = t;
}
for(int i = 1; i <= n; ++i) {
int p;
scanf("%d", &p);
q[i] = 1.0 * p / 100.0;
}
bfsl(1, 0);//以1为根
g[1] = f[1];
bfsq(1, 0);//换根
printf("%.6lf", 1.0 * n - ans);//ans是每个点不发光的概率,用总数减去就是答案了
return 0;
}