BZOJ 3566.[SHOI2014]概率充电器

题目大意

有n个点的树,第i个节点有Xi的概率直接充电,第j条边有Yj的概率导电。
求期望有电的结点数。

题解

又是期望?
怎么直接求有电的期望?由于要考虑很多事件的集合上的关系,直接求不太客观。
那就求每个点没电的期望。
看一下题目给的条件:
①每个点可以向它的父亲导电,也可以向它的儿子导电。(直接进行树形DP看上去很难,因为父亲/儿子都可以转移给x)
②考虑什么情况下点x会有电。当且仅当x自己导电或父亲/儿子有电且与x的连边导电。
从上面的文段可知,转移给x的只有2种情况:
①fa[x]转移给x。②son[x]转移给x。
由于这是概率DP,我们又知P(A)*P(B)=P(A∩B),所以我们可以考虑只有①情况的概率,还有只有②的概率,两种情况的积就是期望没电的概率。
设:
f[x] 表示不考虑fa[x]的情况下x没电的概率,
g[x] 表示fa[x]没有传电给x的概率。
这样子就够了?
不行。根据以上的 f[x] g[x] 因为你一定会想到:
g[x]=f[fa[x]]g[fa[x]]+(1f[fa[x]]g[fa[x]])(1线)
然而 f[fa[x]]g[fa[x]] 是包含了x会影响fa[x]的情况,这样子算的概率会重复。
所以我们要排除x对fa[x]的干扰。
所以,设 h[x] 表示x对fa[x]的不导电的概率。
那么 f[fa[x]]g[fa[x]]/h[x] 就排除了干扰。PS:注意 h[x] 可能等于0,这种情况下
g[x]=1线 ,意思是x一定会导电给fa[x]。

f[x]=(1X[x])+Π(f[son[x]]+(1f[son[x]])(1线))

g[x]=f[fa[x]]g[fa[x]]/h[x]+(1f[fa[x]]g[fa[x]]/h[x])(1线)

注意一下 h[x]=0 的情况。
最后, ans=Σ1f[i]g[i]
对这道题(概率DP)的心得:
①遇到问题像每一个点只属于两种状态A,B的,无论是求概率还是求方案数:
如果求属于A的情况要讨论有很多种情况或是式子很复杂,那么看下:
如果属于B的情况可以由很多个子条件的并集表示,那么尽量去考虑B。
进而A的方案数/概率就是所有情况-B的。
②有多种途径转移到这个状态的,那么可以根据概率的公式,分别考虑每一种情况,然后再合起来。
③对于树形DP的实现方法,我们一定要知道各语句的执行顺序,保证被转移的状态已经是最优的。具体来说,就是先dfs还是先算f[]。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 500010
#define DB double 
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
struct note{
    int to,next;
    DB val;
};note edge[N*2];
int i,j,n,k;
int tot,u,v,w;
int head[N];
int fa[N];
DB f[N],g[N],h[N],x[N];
DB ans,temp;
int read(){
    int fh=1,rs=0;char ch;
    while((ch<'0'||ch>'9')&& ch!='-')ch=getchar();
    if(ch=='-')fh=-1,ch=getchar();
    while(ch>='0'&&ch<='9')rs=rs*10+ch-'0',ch=getchar();
    return rs;
}
void lb(int x,int y,DB z){
    edge[++tot].to=y;
    edge[tot].next=head[x];
    edge[tot].val=z;
    head[x]=tot;
}
void dg(int x){
    for(int i=head[x];i;i=edge[i].next)
        if(fa[x]!=edge[i].to){
            fa[edge[i].to]=x;
            dg(edge[i].to);
            h[edge[i].to]=f[edge[i].to]+(1.0-f[edge[i].to])*(1.0-edge[i].val);
            f[x]*=h[edge[i].to];
        }
}
void dg1(int x){
    for(int i=head[x];i;i=edge[i].next)
        if(fa[x]!=edge[i].to){
            if(h[edge[i].to]<1e-8)temp=0.0;
            else temp=g[x]*f[x]/h[edge[i].to];
            g[edge[i].to]=temp+(1.0-temp)*(1.0-edge[i].val);
            dg1(edge[i].to);
        }
}
int main(){
    n=read();
    fo(i,1,n-1){
        u=read(),v=read(),w=read();
        temp=(DB)w/100;
        lb(u,v,temp);
        lb(v,u,temp);
    }
    fo(i,1,n){
        temp=read();
        x[i]=(DB)temp/100;
        f[i]=1.0-x[i];
    }
    g[1]=1;
    dg(1);
    dg1(1);
    fo(i,1,n)ans=ans+1-f[i]*g[i];
    printf("%.6lf",ans);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值