[BZOJ3566]-[SHOI2014]概率充电器-期望+树形dp

说在前面

并没有什么想说的,但是要保持格式=w=
(肚子疼得1B…简直有毒)


题目

BZOJ3566传送门
不是权限题
看题可进传送门


解法

首先要知道这几个公式(A,B独立,发生条件互不影响):
P(AB)=P(A)P(B) P ( A ⋂ B ) = P ( A ) ∗ P ( B )
意思是:A和B同时发生的概率,等于A,B发生概率的乘积

P(AB)=P(A)+P(B)P(AB) P ( A ⋃ B ) = P ( A ) + P ( B ) − P ( A ⋂ B )
意思是:A和B至少有一个发生的概率,等于A发生的概率+B发生的概率 - A,B同时发生的概率

P(A)=P(AB)P(B)1P(B) P ( A ) = P ( A ⋃ B ) − P ( B ) 1 − P ( B )
这个式子可以直接由上面那个推导得到
可以这么理解:A发生的概率,等于(在B不发生时,A发生的概率)除以(B不发生的概率)

然后就可以开始做题了,分开讨论子树和父亲对 当前节点 的贡献。
先dfs一遍,求出每个节点依靠自身或子树通电的概率。这样可以得到根节点通电的概率,因为根节点要么本身有电,要么靠儿子导电,这两个概率是独立的(不会出现 根节点先有电,然后导电给儿子,儿子再导电给自己 的情况),可以类似的推广下去,所以这样求是正确的

然后再dfs一遍,求出 当前节点 被父亲节点通电的概率。用上面的第三个公式,可以得到(父节点不受当前节点影响而通电的概率)。结合第一次dfs的信息,就可以求出当前节点通电的概率


下面是自带大常数的代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

const double eps = 1e-9 ;
int N , tp , head[500005] ;
struct Path{
    int pre , to ;
    double prob ;
} p[1000005] ;

void In( int &t1 , int &t2 , double t3 ){
    p[++tp] = ( Path ){ head[t1] , t2 , t3 } ; head[t1] = tp ;
    p[++tp] = ( Path ){ head[t2] , t1 , t3 } ; head[t2] = tp ;
}

int cmp( double x ){
    if( x < eps && x > -eps ) return 0 ;
    return x > eps ? 1 : -1 ;
}
double Merge( double x , double y ){ return x + y - x * y ; }
double Split( double x , double y ){ return cmp( 1 - y ) == 0 ? 1 : ( x - y ) / ( 1.0 - y ) ; }

double Light_sub[500005] , Light_fa[500005] , Light[500005] , ans ;//sub = subtree
void dfs1( int u , int f ){
    double &now = Light_sub[u] /= 100 ;
    for( int i = head[u] ; i ; i = p[i].pre ){
        int v = p[i].to ;
        if( v == f ) continue ;
        dfs1( v , u ) ;
        now = Merge( now , Light_sub[v] * p[i].prob ) ;
    }
}

void dfs2( int u , int f ){
    for( int i = head[u] ; i ; i = p[i].pre ){
        int v = p[i].to ;
        if( v == f ) continue ;
        Light_fa[v] = p[i].prob * Split( Light[u] , Light_sub[v] * p[i].prob ) ;
        ans += ( Light[v] = Merge( Light_fa[v] , Light_sub[v] ) ) ;
        dfs2( v , u ) ;
    }
}

void solve(){
    dfs1( 1 , 1 ) ;
    ans += ( Light[1] = Light_sub[1] ) ;
    dfs2( 1 , 1 ) ;
    printf( "%.6f\n" , ans ) ;
}

int main(){
    scanf( "%d" , &N ) ;
    for( int i = 1 , u , v ; i < N ; i ++ ){
        double prob ;
        scanf( "%d%d%lf" , &u , &v , &prob ) ;
        In( u , v , prob/100 ) ;
    }
    for( int i = 1 ; i <= N ; i ++ )
        scanf( "%lf" , &Light_sub[i] ) ;
    solve() ;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值