1. 程式人生 > >【題解】SHOI2014概率充電器

【題解】SHOI2014概率充電器

left struct bool 就是 pac style 轉化 概率 ast

  首先發現答案就是每個節點有電的概率之和。有電的概率牽扯太廣不好求,所以轉化為求沒有電的概率。這題最難的部分在於:一個節點如果有電,可以來自兒子,也可以來自父親。我們考慮將這兩個部分分離開來:建立狀態 \(g[i]\) 和 \(f[i]\) 分別表示一個節點不由子樹節點供電的概率以及不由父親節點供電的概率。

\(g[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 = (1 - p[F]) * f[F] * \frac{g[F]}{g[u] + (1 - g[u]) * w(F, u))} \)

父親不供電給兒子的概率為 :

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

  這樣就解決啦~

#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\n", ans); return 0; }

【題解】SHOI2014概率充電器