题目:http://www.lydsy.com/JudgeOnline/problem.php?id=3566
题意:给定树形结构的n个组件,每个组件有一定概率自己充电,还有一定概率通过某条边给其他组件充电,求充电的组件期望个数。 n≤500000 。
题解:
树形结构肯定能想到树形dp,全树对某点产生的贡献一般可以通过一到两遍树形dp计算得出,本题所求期望等于每个组件被充电的概率之和。
设f[i]
表示i
被充电的概率,g[i]
表示i
被以i
为根的子树充电的概率。
关于g
,g[i]
应该为自己充电的概率和儿子们对其充电的概率的并集,这可由一遍dfs计算得出,概率的并集则可利用公式:P(A + B) = P(A) + P(B) - P(A) * P(B)
。
关于f
,g[i]
已经计算出i被孩子充电的概率,现在只需要计算出i
被父亲充电的概率求并即可,这等价于父亲不被i
充电反给i
充电的概率。考虑一条边e
的父亲是u
,儿子是v
,儿子被父亲充电的概率应该是f[u]
中除去被v
充电的概率后的值,仍可以利用上面的公式拆出:P(A) = [P(A + B) - P(B)] / [1 - P(B)]
。计算f
,除了根没有父亲外,其他点可以再做一遍dfs得出。
两遍树形dp即可,时间复杂度
O(n)
。
代码:
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, double> edge;
const int maxn = 500001;
const double eps = 1e-8;
int n;
vector<edge> e[maxn];
double q[maxn], f[maxn], g[maxn], ans;
inline bool cmp0(double x) { return -eps < x && x < eps; }
inline double merge(double x, double y) { return x + y - x * y; }
inline double split(double r, double y) { return cmp0(1 - y) ? 1 : (r - y) / (1 - y); }
void dfs1(int u, int fa)
{
g[u] = q[u];
for(vector<edge>::iterator it = e[u].begin(), jt = e[u].end(); it != jt; ++it)
if(it -> first != fa)
{
dfs1(it -> first, u);
g[u] = merge(g[u], g[it -> first] * it -> second);
}
}
void dfs2(int u, int fa)
{
ans += f[u];
for(vector<edge>::iterator it = e[u].begin(), jt = e[u].end(); it != jt; ++it)
if(it -> first != fa)
{
f[it -> first] = merge(g[it -> first], split(f[u], g[it -> first] * it -> second) * it -> second);
dfs2(it -> first, u);
}
}
int main()
{
int u, v;
double w;
scanf("%d", &n);
for(int i = 1; i < n; ++i)
{
scanf("%d%d%lf", &u, &v, &w);
w /= 100.0;
e[u].push_back(make_pair(v, w));
e[v].push_back(make_pair(u, w));
}
for(int i = 1; i <= n; ++i)
{
scanf("%lf", q + i);
q[i] /= 100.0;
}
dfs1(1, 0);
f[1] = g[1];
dfs2(1, 0);
printf("%.6f\n", ans);
return 0;
}