BZOJ 3566 [SHOI2014]概率充电器
BZOJ 3566 [SHOI2014]概率充电器
题目描述
著名的电子产品品牌 \(SHOI\) 刚刚发布了引领世界潮流的下一代电子产品——概率充电器:
“采用全新纳米级加工技术,实现元件与导线能否通电完全由真随机数决定!SHOI
概率充电器,您生活不可或缺的必需品!能充上电吗?现在就试试看吧!”
\(SHOI\) 概率充电器由\(n-1\)条导线连通了\(n\)个充电元件。进行充电时,每条导线是否可以导电以概率决定,每一个充电元件自身是否直接进行充电也由概率决定。
随后电能可以从直接充电的元件经过通电的导线使得其他充电元件进行间接充电。
作为 \(SHOI\) 公司的忠实客户,你无法抑制自己购买 \(SHOI\) 产品的冲动。在排了一个星期的长队之后终于入手了最新型号的 \(SHOI\) 概率充电器。
你迫不及待地将 \(SHOI\) 概率充电器插入电源——这时你突然想知道,进入充电状态的元件个数的期望是多少呢?
解题思路
考虑一个已经确定的情况,就是对于边的出现情况和点的通电情况都已经确定的局面。如果分出来的若干连通块内有通电的点,那么期望会加上得到这个连通块的概率*连通块内的点数。其它连通块和这个点相对独立,所以只用单独考虑连通块就好。
一个连通块内有通电的点,我们补集转换一下,用1-全是不通电的点 的概率,就好了。
这样我们只要维护全是不通电的点的概率*大小,剩下的那个类似。
考虑一个不通电连通块的贡献,是若干不连通的边的概率\((1-p)\) 乘上内部连通的边的概率,若干\(p\), 再乘上若干\((1-q)\),即内部全是不通电的概率,最后乘上\(size\)
这样我们合并两个连通块的时候,除了\(sz\)不能直接乘以外,其它的都是可以直接乘起来的。 假设两边的除了sz外的记为\(F1,F2\), 新合并的连通块的贡献就是
这样我们再维护个后面那坨除了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;
}