BZOJ 3566: [SHOI2014]概率充电器

简单的概率DP,思路极其顺畅(然后刚开始还是手残WA了两发)

首先由于这里的每个点贡献都是\(1\),因此期望和就是概率和,换句话说我们要求\(\sum_{i=1}^n P(i)\)\(P(i)\)表示\(i\)被点亮的概率

考虑一个点被点亮的情况,要么是自己亮要么是别人送电给它亮

\(T(P(X),P(Y))\)表示事件\(X,Y\)中至少有一件发生的概率,讨论一下四种情况后得到\(T(P(X),P(Y))=P(X)+P(Y)-P(X)\times P(Y)\),则我们有:

\[P(x)=T(q_x,P(y)\times E_{x,y}) (x,y)\in E \]

woc这式子一看,妈妈我会高斯消元!然后看一眼数据范围,可以得到0分的好成绩……

但是我们仔细一想,这个式子是针对所有图的,然而题目中的是棵树,而树的特点就是无环,转移单一

容易发现在树上一个点被送电的情况只有两种:从子树内来和子树外来(如果我们把它定义成有根树的话)

然后是不是豁然开朗,这就是一个裸的换根DP的套路,我们先一遍求出只从自己子树内转移来的概率

然后再一遍DFS求出从子树外来的概率,这部分可以用父亲的概率消除掉自己转移到父亲的那一部分来计算,直接把上面的式子移个项就能解出来了,注意分母为\(0\)的情况特别考虑

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=500005;
struct edge
{
	int to,nxt; double p;
}e[N<<1]; int head[N],n,cnt,x,y,z; double p[N],up[N],dn[N],ans;
inline void addedge(CI x,CI y,const double& z)
{
	e[++cnt]=(edge){y,head[x],z}; head[x]=cnt;
	e[++cnt]=(edge){x,head[y],z}; head[y]=cnt;
}
inline double T(const double& x,const double& y)
{
	return x+y-x*y;
}
#define to e[i].to
inline void DFS1(CI now=1,CI fa=0)
{
	for (RI i=head[now];i;i=e[i].nxt)
	if (to!=fa) DFS1(to,now),dn[now]=T(dn[now],T(p[to],dn[to])*e[i].p);
}
inline void DFS2(CI now=1,CI fa=0,const double& ep=0)
{
	if (fa)
	{
		up[now]=T(p[fa],up[fa]); double pn=ep*T(p[now],dn[now]);
		if (pn!=1.0) up[now]=T(up[now],(dn[fa]-pn)/(1.0-pn)); up[now]*=ep;
	}
	for (RI i=head[now];i;i=e[i].nxt) if (to!=fa) DFS2(to,now,e[i].p);
}
#undef to
int main()
{
	RI i; for (scanf("%d",&n),i=1;i<n;++i)
	scanf("%d%d%d",&x,&y,&z),addedge(x,y,0.01*z);
	for (i=1;i<=n;++i) scanf("%d",&x),p[i]=0.01*x;
	for (DFS1(),DFS2(),i=1;i<=n;++i) ans+=T(p[i],T(up[i],dn[i]));
	return printf("%.6lf",ans),0;
}
posted @ 2020-02-04 21:21  空気力学の詩  阅读(125)  评论(0编辑  收藏  举报