BZOJ 3566 [SHOI2014]概率充电器

题目:http://www.lydsy.com/JudgeOnline/problem.php?id=3566

题意:给定树形结构的n个组件,每个组件有一定概率自己充电,还有一定概率通过某条边给其他组件充电,求充电的组件期望个数。 n500000

题解:
树形结构肯定能想到树形dp,全树对某点产生的贡献一般可以通过一到两遍树形dp计算得出,本题所求期望等于每个组件被充电的概率之和。
f[i]表示i被充电的概率,g[i]表示i被以i为根的子树充电的概率。
关于gg[i]应该为自己充电的概率和儿子们对其充电的概率的并集,这可由一遍dfs计算得出,概率的并集则可利用公式:P(A + B) = P(A) + P(B) - P(A) * P(B)
关于fg[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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值