[SHOI2014]概率充电器 题解
参考:
https://www.cnblogs.com/iiyiyi/p/5780368.html
https://www.luogu.com.cn/blog/asuldb/solution-p4284
%%%
【树形期望DP】[SHOI2014]概率充电器
题目大意
由 \(n-1\) 条导线连通了 \(n\) 个元件。这 \(n-1\) 条导线每条都有 \(p\) 的概率能够导电,而每个元件本身有 \(q_i\) 的概率被直接充电。电能可以从直接充电的元件经过通电的导线使得其他充电元件进行间接充电。问期望有多少个元件处于充电状态?
思路
设编号为 \(i\) 的元件被充电的概率为 \(P_i\),设共有 \(X\) 个元件处于充电状态。
我们要求的 \(E(X)\) 就是 \(\sum_{i=1}^n P_i\)
因为 \(n-1\) 条导线连通了 \(n\) 个元件,所以这 \(n\) 个充电元件构成一棵树。任取一个节点作为数根。显然元件 \(i\) 通电有三种可能:
- 它自己来电了
- 它的孩子里有一个点来电了传了过来
- 它的父亲来电了传了过来
第一种情况的概率就是 \(q_i\),第二种情况的概率明显可以用递推来解。至于第三种情况……后面再说
用 \(f_i\) 表示当前元件仅仅因为直接充电或由孩子供电的概率。
对于树中叶子节点 \(i\),它没有孩子,所以\(f_i=q_i\)。
对于非叶子节点 \(i\),\(f_i=q_i + \sum_{j\in \operatorname{son}(i)} f_j\cdot p_j\)
吗?
不对!
对于两个相互独立的事件 \(A\)、\(B\),
这其实是“容斥原理”
所以,上面的加法都要换为概率和。方便起见,我把数字 \(a\) 和 \(b\) 的“概率和”记为 \(a\cup b=a+b-ab\),把集合 \(A\) 中所有元素的“概率和”记为 \(\bigcup_{i\in A} i\)
所以 \(f_i = q_i \cup \bigcup_{j\in\operatorname{son}(i)} f_j\cdot p_j\)
这样,我们就可以求出每个元素的 \(f\) 了。写成表达式就是
现在把由父亲充电的情况加进来!
最简单的是根节点。对于根节点 \(i\),它没有父亲,所以 \(P_i=f_i\)
对于其他的节点 \(i\),它有个父亲 \(\textit{fa}\),递推式为 \(P_i=f_i\cup P_{\textit{fa}}\cdot p\)
这样,再从根节点向叶子节点进行一次遍历,就可以把每个节点的充电概率算出来了。
参考代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define EPS (1e-8)
using namespace std;
const int MAXN=500000+50;
struct node {
int to;
double p;
};
vector<node> E[MAXN];
double q[MAXN],f[MAXN],P[MAXN],ans;
void addedge(int a,int b,double p) {
E[a].push_back((node){b,p});
}
bool dcmp(double a) {
return fabs(a-0)<EPS;
}
void dfs1(int u,int fa) {
for (int i=0;i<E[u].size();i++) {
int to=E[u][i].to;
if (to!=fa) {
dfs1(to,u);
f[u]=f[u]+f[to]*E[u][i].p-f[u]*f[to]*E[u][i].p;
}
}
}
void dfs2(int u,int fa) {
ans+=P[u];
for (int i=0;i<E[u].size();i++) {
int to=E[u][i].to;
if (to!=fa) {
if (dcmp(1.0-E[u][i].p*f[to])) P[to]=1.0;
else {
double tmp=(P[u]-E[u][i].p*f[to])/(1.0-E[u][i].p*f[to]);
P[to]=f[to]+tmp*E[u][i].p-f[to]*tmp*E[u][i].p;
}
dfs2(to,u);
}
}
}
void init() {
int n;
scanf("%d",&n);
for(int i=0;i<n-1;i++) {
int a,b;
double p;
scanf("%d%d%lf",&a,&b,&p);
addedge(a,b,p/100);
addedge(b,a,p/100);
}
for (int i=1;i<=n;i++) {
scanf("%lf",&q[i]);
f[i]=q[i]/100;
}
}
void solve() {
dfs1(1,0);
P[1]=f[1];
dfs2(1,0);
printf("%.6lf",ans);
}
int main() {
init();
solve();
return 0;
}