【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\)的转移方程分别为:

\[f_x=U_{y∈son_x}(U(p_y,f_y)\times P_{edge})\]

即将通过每个子树被标记的概率统计起来。

\[y∈son_x,g_y=U(U(p_x,g_x),T(f_x,U(p_y,f_y)\times P_{edge}))\times P_{edge}\]

其中\(U(p_x,g_x)\)是\(x\)通过自身及子树外节点被标记的概率,\(T(f_x,U(p_y,f_y)\times P_{edge})\)是\(x\)通过除\(y\)的子树以外其余子树被标记的概率。

应该还是比较好理解的。

最后每个节点被标记的概率就是:

\[U(p_x,U(f_x,g_x))\]

代码

#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;//统计答案并输出
}
上一篇:linux命令a---arp


下一篇:蓝桥杯——牌型种数