【洛谷 P4284】【树形DP】【期望】 概率充电器
题目
解题思路
考试的时候就很懵
样例都没推出来
???我大概是脑子烧坏了
直接求出开的概率还是很难想的
可以想到求出关的概率
然后用1-关的概率就是开的概率
假设将图建出来长这个样子
设 f[i] 是第i个节点不充电的概率 (其子树以及它自己转移过来的概率)
考虑 j 这个节点能对 i 做出什么贡献
既然要让 i 是关的 那么ta自己得是关的 边不能传电 或是 j 没有电边能传电
那么式子就是
f[i] = (1-p[i])(1 - h[i][j] + h[i][j] * f[j]) (j是i的子节点)
这样求出来的只有根是确定的概率
别的点并不确定
假设我们要 j 为根
那么就会分为三个部分 原有的子树 根除ta的部分 根与它的连接
设 g[i] 为第i个点关的概率(所有情况)
如果 i 是第一次求时的根 g[i]=f[i]
否则 先求出根除ta的部分(除去ta的贡献即可)
res = g[fa] / (1-h[fa][i] + h[fa][i] * f[i])
然后将这贡献放入g[i]中
g[i] = f[i] * (1-h[fa][i] + h[fa][i] * res)
最后答案就是累加所有的1-g[i]
关于在gmoj只有80这件事,不知为啥RE了
代码
#include<iostream>
#include<cstdio>
using namespace std;
struct lzf{
int to,next;
double z;
}h[1000010];
int n,x,y,t,z,head[500010];
double ans,p[500010],f[500010],g[500010];
void add(int x,int y,int z)
{
h[++t].to=y;
h[t].z=z*0.01;
h[t].next=head[x];
head[x]=t;
h[++t].to=x;
h[t].z=z*0.01;
h[t].next=head[y];
head[y]=t;
}
void dfs(int id,int fa)
{
f[id]=1-p[id]; //自己不开的概率
for (int i=head[id];i;i=h[i].next)
if (h[i].to!=fa)
{
dfs(h[i].to,id);
f[id]*=(1-h[i].z+h[i].z*f[h[i].to]); //乘上子节点不开的概率
}
}
void zdfs(int id,int fa,int b) //b是指用的哪条边,因为要知道这条边的概率
{
if (id==1)
g[id]=f[id]; //是根,直接取值
else{
double res=g[fa]/(1-h[b].z+h[b].z*f[id]);
g[id]=f[id]*(1-h[b].z+h[b].z*res); //更新答案
}
for (int i=head[id];i;i=h[i].next)
if (h[i].to!=fa)
zdfs(h[i].to,id,i);
}
int main()
{
scanf("%d",&n);
for (int i=1;i<n;i++)
{
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
}
for (int i=1;i<=n;i++)
{
scanf("%lf",&p[i]);
p[i]*=0.01;
}
dfs(1,0); //先求出根的概率
zdfs(1,0,0); //求所有点的概率
for (int i=1;i<=n;i++)
ans+=1-g[i];
printf("%.6lf",ans);
return 0;
}