看的shadowice1984的题解,写的确实很棒,大概挑一点摘出来吧
这里想讲一个关于概率题的小技巧,就是关于如何求某个事件发生的概率PP,事实上大家也清楚,除了一些特殊的近似算法之外,我们在程序中计算概率的方法无非就是加减乘除四则运算而已……而减法和除法又是加法和乘法的逆。
而在概率角度上,应该各位都是知道乘法原理和加法原理的,乘法意味着独立事件,而加法意味着互斥事件……,另外一个概率中很常见的等式是所有事件发生的概率和等于1
因此概率上来讲,如果剔除掉近似算法的话,我们其实只有3个方程可以使用(这里不讨论条件概率……)
1.乘法原理
2.加法原理
3.所有概率和等于1
因此我们在求概率的过程本质上就是在做一件事,将这个事件拆分成若干个要么独立要么互斥的事件,然后期间可能需要使用若干次求补事件的转化……
确实非常受益,原来对于概率期望这块还很糊涂,现在清楚一些了
首先题目中给出的一个点被充电的条件是自己有电或周边点有电且连接的边导电吗,注意到或的关系不可以用乘法原理描述,因此我们取补之后变化为且的关系,所以只需要求出来每个点没电的概率,然后用1去减就是了
当时讲的时候一直不知道为什么要转化成不被充电的概率...
这题用到的子树和子树外两个f,g数组应该是树形dp常用技巧...原来做这样的题比较少
求f,g过程:
现在的问题是,什么时候点v没法给i充电?,有两种情况,1.点v没电,2.点v有电且边不导电,这两种情况显然互斥,所以可以加起来
设$pi$为$i$自己充电概率,$val_{i,j}$为边联通概率,
由上面那句话:$f[i]=(1-pi)*prod_{v in son}f[v]+(1-f[v])*(1-val_{i,v})$
$g$是由 除$fa[i]$所在子树之外的部分 和 除$i$所在子树之外的$fa[i]$的子树 组成的
前面的好说就是$g[fa[i]]$,后面这个既然是除$i$之外的所有子树...还是看题解吧
那么我们观察到在换根到v的过程中,其实新的外部连通块是由v的父亲的外部联通块加上一个v的父亲的子树刨掉v的子树构成的连通块,我们知道dp的时候是以连乘积的形式转移的,因此想要求刨掉某个子树后剩下的Dp值就直接除就可以了
此时每个点不被充电的概率=$f[i]*(g[fa[i]]+(1-g[fa[i]])*(1-val_{fa[i],i}))$与$f[i]$类似
这样$g[i]=frac{每个点不被充电的概率}{f[i]+(1-f[i])*(1-val_{fa[i],i})}$
#include<bits/stdc++.h> using namespace std; const int maxn=500009; int n; struct node{ int v,nxt;double w; }e[maxn<<1]; int head[maxn],cnt; void add(int u,int v,double w){ e[++cnt].v=v;e[cnt].nxt=head[u];head[u]=cnt;e[cnt].w=w; } double q[maxn]; double f[maxn],g[maxn],ans[maxn]; //ans[i]=g[fa[i]]+(1-g[fa[i]])*(1-val(fa[i],i)) int fa[maxn]; void dfs1(int x,int ff){ fa[x]=ff;f[x]=(1.0-q[x]); for(int i=head[x];i;i=e[i].nxt){ int y=e[i].v; if(y==ff)continue; dfs1(y,x); f[x]*=f[y]+(1.0-f[y])*(1.0-e[i].w); } } void dfs2(int x){ for(int i=head[x];i;i=e[i].nxt){ int y=e[i].v;if(y==fa[x])continue; double g=ans[x]*f[x]/(f[y]+(1.0-f[y])*(1.0-e[i].w)); // printf("%lf ",g); ans[y]=g+(1.0-g)*(1.0-e[i].w); dfs2(y); } } int main(){ scanf("%d",&n); for(int i=1,u,v,w;i<n;i++){ scanf("%d%d%d",&u,&v,&w); add(u,v,w/100.0);add(v,u,w/100.0); } for(int i=1,w;i<=n;i++)scanf("%d",&w),q[i]=w/100.0; ans[1]=1; dfs1(1,1);dfs2(1); double sum=0; for(int i=1;i<=n;i++)sum+=1-ans[i]*f[i]; printf("%.6lf ",sum); // for(int i=1;i<=n;i++)printf("%.3lf ",ans[i]); }