[SHOI2014] 概率充电器

首先记 \(f_u\) 表示 \(u\) 不被以 \(u\) 为根的子树内点通上电的概率。

不难列出这样一个式子:

\(f_u=\displaystyle(1-p_u)*\prod_{v \in subtree\ u}f_v*e(u,v)\)

其中 \(p_u\) 表示直接充 \(u\) 的概率。

但是这个式子有个 bug,它处理不了给 \(u\) 导电的点在 \(u\) 子树外的情况。

那么我们采取换根的思想,记 \(g_u\) 表示 \(u\) 不通电的概率,则 \(g_u\) 可以通过 \(u\) 的子树和剩余部分计算,子树部分我们已经处理过,而剩余部分可以通过 \(u\) 的父亲计算,记 \(h_u\) 表示非 \(u\) 子树里的点导电给 \(u\) 的概率。

那么 \(h_u = \dfrac{g_{fa}}{1-e(fa,u)+e(fa,u)*f_u}\)

那么 \(g_u = f_u*(1-e(fa,u)+e(fa,u)*f_u)\)

那么答案就是 \(\displaystyle \sum_{i=1}^{n}(1-g_i)\)

#include <bits/stdc++.h>
#define reg register
#define ll long long
using namespace std;
const int MAXN = 5e5 + 10;
int n, E, head[MAXN], nxt[MAXN << 1], pnt[MAXN << 1];
double val[MAXN << 1], p[MAXN], f[MAXN], g[MAXN], h[MAXN];
inline void clear() {
  E = 0, memset(head, -1, sizeof(head));
}
inline void addedge(int x, int y, double v) {
  nxt[E] = head[x], pnt[E] = y, val[E] = v, head[x] = E++;
}
inline void dfs1(int u, int fa) {
  f[u] = 1 - p[u];
  for(reg int i = head[u]; i != -1; i = nxt[i]) {
    int v = pnt[i];
    if(v == fa) continue;
    dfs1(v, u), f[u] *= (1 - val[i] + val[i] * f[v]);
  }
}
inline void dfs2(int u, int fa, int id) {
  if(u == 1) g[u] = f[u];
  else h[u] = g[fa] / (1 - val[id] + val[id] * f[u]), g[u] = f[u] * (1 - val[id] + val[id] * h[u]);
  for(reg int i = head[u]; i != -1; i = nxt[i]) {
    int v = pnt[i];
    if(v == fa) continue;
    dfs2(v, u, i);
  }
}
inline void work() {
  scanf("%d", &n), clear();
  for(reg int i = 1; i < n; ++i) {
    int u, v;
    double d;
    scanf("%d%d%lf", &u, &v, &d), d *= 0.01, addedge(u, v, d), addedge(v, u, d);
  }
  for(reg int i = 1; i <= n; ++i) scanf("%lf", &p[i]), p[i] *= 0.01;
  dfs1(1, 0), dfs2(1, 0, 0);
  double ans = 0;
  for(reg int i = 1; i <= n; ++i) ans += 1.0 - g[i];
  printf("%.6f\n", ans);
}
int main() {
  int _ = 1;
  // scanf("%d", &_);
  while(_--) work();
  return 0;
}

posted @ 2020-09-12 22:37  Nylch  阅读(72)  评论(0编辑  收藏  举报