BZOJ 3566 [SHOI2014]概率充電器
BZOJ 3566 [SHOI2014]概率充電器
題目描述
著名的電子產品品牌 \(SHOI\) 剛剛釋出了引領世界潮流的下一代電子產品——概率充電器:
“採用全新奈米級加工技術,實現元件與導線能否通電完全由真隨機數決定!SHOI
概率充電器,您生活不可或缺的必需品!能充上電嗎?現在就試試看吧!”
\(SHOI\) 概率充電器由\(n-1\)條導線連通了\(n\)個充電元件。進行充電時,每條導線是否可以導電以概率決定,每一個充電元件自身是否直接進行充電也由概率決定。
隨後電能可以從直接充電的元件經過通電的導線使得其他充電元件進行間接充電。
作為 \(SHOI\) 公司的忠實客戶,你無法抑制自己購買 \(SHOI\)
你迫不及待地將 \(SHOI\) 概率充電器插入電源——這時你突然想知道,進入充電狀態的元件個數的期望是多少呢?
解題思路
考慮一個已經確定的情況,就是對於邊的出現情況和點的通電情況都已經確定的局面。如果分出來的若干連通塊內有通電的點,那麼期望會加上得到這個連通塊的概率*連通塊內的點數。其它連通塊和這個點相對獨立,所以只用單獨考慮連通塊就好。
一個連通塊內有通電的點,我們補集轉換一下,用1-全是不通電的點 的概率,就好了。
這樣我們只要維護全是不通電的點的概率*大小,剩下的那個類似。
考慮一個不通電連通塊的貢獻,是若干不連通的邊的概率\((1-p)\)
\[F(u)=sz\prod(1-p)\prod p \prod (1-q) \]
這樣我們合併兩個連通塊的時候,除了\(sz\)不能直接乘以外,其它的都是可以直接乘起來的。 假設兩邊的除了sz外的記為\(F1,F2\), 新合併的連通塊的貢獻就是
\[(Sz1+Sz2)*F1*F2=Sz1*F1*F2+Sz2*F2*F1 \]
這樣我們再維護個後面那坨除了sz以外的東西,兩邊互相乘一下就好。 由於我們維護u的時候沒有考慮u的父邊,所以再把u的父邊的概率乘一下。
#include<bits/stdc++.h> using namespace std; #define db double const int N = 5e5 + 11; int n, w[N]; int head[N], nex[N<<1], to[N<<1], wei[N<<1], size; db ans, F[N], G[N], E[N], D[N]; void add(int x, int y, int z){ to[++size] = y; nex[size] = head[x]; head[x] = size; wei[size] = z; } void dfs(int u, int fa){ G[u] = F[u] = (1.0 - 1.0 * w[u] / 100); E[u] = D[u] = 1; for(int i = head[u];i;i = nex[i]){ int v = to[i]; if(v == fa)continue; dfs(v, u); db p = 1.0 * wei[i] / 100; ans += (1.0 - p) * (E[v] - F[v]); F[v] *= p; G[v] *= p; E[v] *= p; D[v] *= p; F[u] = F[u] * G[v] + G[u] * F[v] + F[u] * (1.0 - p); G[u] = G[u] * G[v] + G[u] * (1.0 - p); E[u] = E[u] * D[v] + E[v] * D[u] + E[u] * (1.0 - p); D[u] = D[u] * D[v] + D[u] * (1.0 - p); } } int main(){ cin>>n; int u, v, w; for(int i = 1;i < n; i++){ scanf("%d%d%d", &u, &v, &w); add(u, v, w); add(v, u, w); } for(int i = 1;i <= n; i++){ scanf("%d", &::w[i]); } dfs(1, 1); ans += E[1] - F[1]; printf("%.6lf\n", ans); return 0; }