[BZOJ3566][SHOI2014]概率充电器
sol
很显然的两遍树DP吧。
设\(f_i\)表示只考虑\(i\)的子树,\(i\)点没有电的概率。
\[f_i=q_i*\prod_{v是i的儿子}f_v+(1-f_v)*(1-p_e)
\]
为了方便表示把后面那一坨记为\(h_v\),即\(h_i=f_i+(1-f_i)*(1-p_e)\)
然后再记一个\(g_i\)表示\(i\)的父亲不向\(i\)供电的概率,那么\(g_i=f_{fa}*g_{fa}/h_i\)。
因为\(h_i\)可能等于\(0\),所以第二次DP的时候要记一下所有儿子中\(h_v=0\)的个数,判一下即可。
最终\(ans=\sum_{i=1}^{n}1-f_i*g_i\)
code
#include<cstdio>
#include<algorithm>
using namespace std;
int gi()
{
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 5e5+5;
int n,to[N<<1],nxt[N<<1],head[N],cnt;
double ww[N<<1],val[N],f[N],g[N],h[N],ans;
void link(int u,int v,int w)
{
to[++cnt]=v;nxt[cnt]=head[u];ww[cnt]=(double)w/100.0;
head[u]=cnt;
}
void dfs1(int u,int fa)
{
f[u]=val[u];
for (int e=head[u];e;e=nxt[e])
if (to[e]!=fa)
{
dfs1(to[e],u);
h[to[e]]=f[to[e]]+(1-f[to[e]])*(1-ww[e]);
f[u]*=h[to[e]];
}
}
void dfs2(int u,int fa)
{
double sum=val[u];int z=0;
for (int e=head[u];e;e=nxt[e])
if (to[e]!=fa)
if (h[to[e]]>1e-9) sum*=h[to[e]];
else ++z;
for (int e=head[u];e;e=nxt[e])
if (to[e]!=fa)
{
double t;
if (h[to[e]]>1e-9)
t=(z?0:sum/h[to[e]]*g[u]);
else t=(z>1?0:sum*g[u]);
g[to[e]]=t+(1-t)*(1-ww[e]);
dfs2(to[e],u);
}
}
int main()
{
n=gi();
for (int i=1;i<n;++i)
{
int u=gi(),v=gi(),w=gi();
link(u,v,w);link(v,u,w);
}
for (int i=1;i<=n;++i)
{
int x=gi();
val[i]=1.0-(double)x/100.0;
}
dfs1(1,0);g[1]=1;dfs2(1,0);
for (int i=1;i<=n;++i) ans+=1.0-f[i]*g[i];
printf("%.6lf\n",ans);
return 0;
}