【题解】SHOI2014概率充电器

  首先发现答案就是每个节点有电的概率之和。有电的概率牵扯太广不好求,所以转化为求没有电的概率。这题最难的部分在于:一个节点如果有电,可以来自儿子,也可以来自父亲。我们考虑将这两个部分分离开来:建立状态 (g[i]) 和 (f[i]) 分别表示一个节点只考虑子树节点没有电的概率以及不由父亲节点供电的概率。

(g[u] = (1 - p[u])prod (g[v] + (1 - g[v]) * (1 - w(u, v))]))

其中(v) 为 (u) 的子节点,(w(u, v)) 为 (u, v) 边有电的概率

  利用这个递推式我们可以dfs一遍自下而上获取所有节点的 (g[u]);

  然后考虑如何求得 (f[u])。要注意由于 (f[u]) 是 (u) 的父亲不供电给 (u) 的概率,所以在利用父亲的信息时应该要除去儿子的影响:

父亲 (F) 没有电的概率 (P = f[F] * frac{g[F]}{g[u] + (1 - g[u]) * (1 - w(F, u)))} )

父亲不供电给儿子的概率为 :

(f[u] = P + (1 - P) * (1 - w(F, u)))

  这样就解决啦~(代码中的 (f, g) 与上述描述相反,早期代码请勿介意……)

#include <bits/stdc++.h>
using namespace std;
#define maxn 505000
#define db double
#define eps 0.0000001

int n, cnp = 1;
int head[maxn];
db ans, p[maxn], f[maxn], g[maxn];

struct edge
{
    int to, last; db co;
}E[maxn * 2];

void add(int u, int v, db w)
{
    E[cnp].to = v, E[cnp].co = w;
    E[cnp].last = head[u], head[u] = cnp ++;
}

bool check(db x) { return abs(x - 0.0) < eps; }

void dfs(int u, int fa)
{
    f[u] = 1.0;
    for(int i = head[u]; i; i = E[i].last)
    {
        int v = E[i].to;
        if(v == fa) continue;
        dfs(v, u);
        f[u] *= f[v] + (1.0 - f[v]) * (1.0 - E[i].co);
    } 
    f[u] *= (1.0 - p[u]);
} 

void dfs2(int u, int fa)
{
    for(int i = head[u]; i; i = E[i].last)
    {
        int v = E[i].to;
        if(v == fa) continue;
        db P = g[u] * f[u] / (f[v] + (1.0 - f[v]) * (1.0 - E[i].co));
        g[v] = P + (1.0 - P) * (1.0 - E[i].co);
        dfs2(v, u);
    }
    ans += 1.0 - (g[u] * f[u]);
}

int main()
{
    scanf("%d", &n);
    for(int i = 1; i < n; i ++)
    {
        int a, b, p;
        scanf("%d%d%d", &a, &b, &p);
        add(a, b, (db) p / 100.0);
        add(b, a, (db) p / 100.0);
    }
    for(int i = 1; i <= n; i ++) scanf("%lf", &p[i]), p[i] /= 100.0;
    g[1] = 1.0;
    dfs(1, 0); dfs2(1, 0);
    printf("%.6lf
", ans);
    return 0;
}

 

原文地址:https://www.cnblogs.com/twilight-sx/p/9343716.html