【BZOJ3566】[SHOI2014] 概率充电器(树形DP)
大致题意: 有一棵树,其中每条边有一定的概率存在。每个点有一定概率被直接标记,与被标记的点连通的点也会被标记。求被标记的点数的期望。
前言
\(Jan\ 28th\)刷题计划(2/6),算法标签:树、DP、概率论。
挺简单的一道\(DP\)题吧。
转化
点数的期望其实可以分到每个点上,就相当于每个点的期望之和。
由于一个点的贡献是\(1\),而期望=概率*贡献,所以此处点数的期望就相当于每个点被标记概率之和。
那么,我们只要求出每个点被标记的概率即可。
定义新运算
考虑一个问题:如果一个点有\(x\)的概率通过第一种方式被标记,又有\(y\)的概率通过第二种方式被标记,求这个点被标记的概率。
这个概率应该是\(x+y-xy\)(之所以要减掉\(xy\),是因为如果通过两种方式都标记了它就会计算重复,故减去)。
方便后文描述,我们定义,\(U(x,y)=x+y-xy\)。
再考虑一个问题:如果一个点有\(x\)的概率通过某种方式被标记,而这个点被标记的总概率是\(z\),求这个点以其他方式被标记的概率\(y\)。
因为\(z=U(x,y)=x+y-xy\),所以变形一下就得到了\(y=\frac{z-x}{1-x}\)。
考虑到当\(x=1\)时,\(y\)为任何值都能使原式成立,所以此时我们可以随便给它赋个值,姑且令此时\(y=1\)。
方便后文描述,我们定义,\(T(z,x)=\begin{cases}1&(x=1)\\\frac{z-x}{1-x}&(x≠1)\end{cases}\)。
动态规划
我们设\(f_x\)为\(x\)通过其子树内的点被标记的概率,\(g_x\)为\(x\)通过其子树外的点被标记的概率(这里的子树内和子树外都不包括\(x\)自身)。
容易得到\(f_x\)和\(g_x\)的转移方程分别为:
即将通过每个子树被标记的概率统计起来。
其中\(U(p_x,g_x)\)是\(x\)通过自身及子树外节点被标记的概率,\(T(f_x,U(p_y,f_y)\times P_{edge})\)是\(x\)通过除\(y\)的子树以外其余子树被标记的概率。
应该还是比较好理解的。
最后每个节点被标记的概率就是:
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 500000
#define DB double
#define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].p=z/100.0)
#define U(x,y) ((x)+(y)-(x)*(y))
#define T(z,x) ((x)==1?1:((z)-(x))/((1)-(x)))
using namespace std;
int n,ee,lnk[N+5];DB p[N+5],f[N+5],g[N+5];struct edge {int to,nxt;DB p;}e[N<<1];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define tn (x<<3)+(x<<1)
#define D isdigit(c=tc())
char c,*A,*B,FI[FS];
public:
I FastIO() {A=B=FI;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
}F;
I void DP1(CI x,CI lst=0)//第一个树形DP,转移f
{
for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&
(DP1(e[i].to,x),f[x]=U(f[x],U(p[e[i].to],f[e[i].to])*e[i].p));
}
I void DP2(CI x,CI lst=0)//第二个树形DP,转移g
{
for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&
(g[e[i].to]=U(U(p[x],g[x]),T(f[x],U(p[e[i].to],f[e[i].to])*e[i].p))*e[i].p,DP2(e[i].to,x),0);
}
int main()
{
freopen("a.in","r",stdin);
RI i,x,y,z;for(F.read(n),i=1;i^n;++i) F.read(x),F.read(y),F.read(z),add(x,y,z),add(y,x,z);
for(i=1;i<=n;++i) F.read(z),p[i]=z/100.0;DB ans=0;DP1(1),DP2(1);
for(i=1;i<=n;++i) ans+=U(p[i],U(f[i],g[i]));return printf("%.6lf",ans),0;//统计答案并输出
}