很好的概率+树形Dp啊,这是第二次写了,第一次写是2月份,然后……写炸了,因为不会写从父亲到儿子的Dfs,所以就拖了将近……5个月(滑稽),总算还是A掉了
原谅我看了题解验证我的公式是不是对的
好啦开始吧:
这不是很显然的吗,首先定义Dp方程 F 和 G 分别表示儿子可以为父亲充上电的概率,和父亲可以为儿子充上电的概率
但是怎么转移呢?Emmm看样子很难转移啊,因为计算儿子被充上电的概率还是很繁琐的,因为可能有些儿子没有电,有些儿子又有,然而边又有可能不导电,如果要一个一个枚举,岂不炸了?
可不可以补集转化呢?重新定义 F 和 G 分别表示儿子不可以为父亲充上电的概率,和父亲不可以为儿子充上电的概率
那么转移就很方便了,先看 F 如何转移吧:
0、自己有电就不用考虑儿子充不充的上了,所以一开始不被儿子充电的概率是自己没电的概率
1、首先某个儿子自己没有电,那么就一定不可能充上电
2、其次某个儿子有电了,但是连接它的那条边没有导电,那么也不会被该儿子充上电
1 和 2 互无交集,所以其概率可以相加,那么所有儿子无法为他充上电的概率就是
可以转移 F 了!
下面来看如和转移 G:
0、首先考虑根节点的 G,根节点没有爸爸,所以他爸爸不给他充上电的概率为1
(这下子有了 G 的初值了,所以可以考虑如何转移呢)
1、那么某一个节点不被充电的概率应该是他爸爸不给他充电并且儿子也不给他充电的概率的乘积0
2、一个儿子结点不被爸爸充上电的概率是什么?是不是要算上 爸爸p不被充上电的概率,还要算上 p被充上电的概率但是又不导电的概率,也就是:
所以可以转移 G 啦
最后每一个点的答案就是
统计一下就好了!
代码:
#include <bits/stdc++.h>
const int N = 1000000 + 5 ;
double f [ N ] , g [ N ] , px [ N ] , e [ N ] ;
double p , ans , ex ;
int head [ N ] , nxt [ N ] , to [ N ] , cn ;
int n , x , y ;
void create ( int u , int v , double p ) {
cn ++ ;
px [ cn ] = p ;
to [ cn ] = v ;
nxt [ cn ] = head [ u ] ;
head [ u ] = cn ;
}
void dfs_son ( int u , int fr ) {
int v ;
f [ u ] = 1 - e [ u ] ;
for ( int i = head [ u ] ; i ; i = nxt [ i ] ) {
v = to [ i ] ;
if ( v == fr ) continue ;
dfs_son ( v , u ) ;
f [ u ] *= ( f [ v ] + ( 1 - f [ v ] ) * ( 1 - px [ i ] ) ) ;
}
}
void dfs_fa ( int u , int fr ) {
int v ;
for ( int i = head [ u ] ; i ; i = nxt [ i ] ) {
v = to [ i ] ;
if ( v == fr ) continue ;
double t = g [ u ] * f [ u ] / ( f [ v ] + ( 1 - f [ v ] ) * ( 1 - px [ i ] ) ) ;
g [ v ] = t + ( 1 - t ) * ( 1 - px [ i ] ) ;
dfs_fa ( v , u ) ;
}
}
int main ( ) {
scanf ( "%d" , & n ) ;
for ( int i = 1 ; i < n ; i ++ ) {
scanf ( "%d%d%lf" , & x , & y , & p ) ;
create ( x , y , p / 100.0 ) ;
create ( y , x , p / 100.0 ) ;
}
for ( int i = 1 ; i <= n ; i ++ ) {
scanf ( "%lf" , & ex ) ;
e [ i ] = ex / 100.0 ;
}
dfs_son ( 1 , 1 ) ;
g [ 1 ] = 1.0 ;
dfs_fa ( 1 , 1 ) ;
for ( int i = 1 ; i <= n ; i ++ )
ans += 1 - f [ i ] * g [ i ] ;
printf ( "%.6lf" , ans ) ;
return 0 ;
}