题目描述
著名的电子产品品牌SHOI 刚刚发布了引领世界潮流的下一代电子产品—— 概率充电器:
“采用全新纳米级加工技术,实现元件与导线能否通电完全由真随机数决 定!SHOI 概率充电器,您生活不可或缺的必需品!能充上电吗?现在就试试看 吧!”
SHOI 概率充电器由n-1 条导线连通了n 个充电元件。进行充电时,每条导 线是否可以导电以概率决定,每一个充电元件自身是否直接进行充电也由概率 决定。随后电能可以从直接充电的元件经过通电的导线使得其他充电元件进行 间接充电。
作为SHOI 公司的忠实客户,你无法抑制自己购买SHOI 产品的冲动。在排 了一个星期的长队之后终于入手了最新型号的SHOI 概率充电器。你迫不及待 地将SHOI 概率充电器插入电源——这时你突然想知道,进入充电状态的元件 个数的期望是多少呢?
n≤500000
题解
这个题目有一点要注意:间接充电也可以给别的元件充电。就是说,只要有电,就有机会可以给别的元件充电。
期望=概率*结果。由于个数贡献都是1,所以,答案就是每个元件被充电的概率之和。
每个元件(以下简称点)被充电的可能情况是:1.直接充电。2.被子树充电。3.被父亲充电。
从子树转移,又要从父亲转移进来,所以肯定就要二次扫描+换根了。
第一次dfs,
f[i]表示,点i直接充电/被子树充电的概率。
f[i]=1-(1-pi)(1-f[y1]*w(x,y1))(1-f[y2]*w(x,y2))...
即,所有点都不给i充电的概率,然后用1减它,就是被其中一个充电的概率了。
第二次dfs
设g[x]表示,x被x的father充电的概率。
特别的,g[1]=0;
因为先后关系,肯定要除掉f[fa]被x充电的概率。
我们就要先求出,fa不被x充电时,被充电的概率k。
那么就是,k=1-(1-pfa)(1-g[fa])(1-f[y1]*w(x,y1))(1-f[y2]*w(x,y2))...其中yi!=x
可以发现,后面的(1-f[y1]*w(x,y1))(1-f[y2]*w(x,y2))...
就是:(1-f[fa])/(1-f[x]*w(fa,x))
然后,g[x]=w(fa,x)*k
最后,一个点被充电的概率就是:ans[x]=1-(1-g[x])(1-f[x])
即,1-不会充电的概率。
将概率做和即可。
但是一个坑点,1-f[x]*w(fa,x)等于0怎么办?
不能除以0.
这个时候,就意味着,f[x]=1,w(fa,x)=1,x一定可以给y充电。
可以讨论一下。
但是更直接地,f[x]一定是1,那么ans[x]一定就是1了。
所以g[x]随便是什么都无所谓了。可以直接传下去0
对于其他的儿子,其实k都是1了。
但是再除一下也无所谓反正k都是1。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=500000+5;
const double eps=0.0001;
int n;
double p[N],f[N],g[N];
double ans[N];
double op;
struct node{
int nxt,to;
int pre;
double v;
}e[2*N];
int hd[N],las[N],cnt;
double pre[N],bac[N];
void add(int x,int y,double z){
if(hd[x]&&e[hd[x]].nxt==0) las[x]=hd[x];
e[++cnt].nxt=hd[x];
e[cnt].to=y;
e[cnt].v=z;
e[hd[x]].pre=cnt;
hd[x]=cnt;
}
void dfs(int x,int fa){
double pi=1.00;
pi*=(1.00-p[x]);
for(int i=hd[x];i;i=e[i].nxt){
int y=e[i].to;
if(y==fa) continue;
dfs(y,x);
pi*=(1.00-e[i].v*f[y]);
}
f[x]=1.00-pi;
}
void slo(int x,int fa,double val,double pf){
if(x!=1){
g[x]=val*(1-(1-g[fa])*pf);
ans[x]=1.00-(1.00-g[x])*(1.00-f[x]);
}
else{
g[x]=0;
ans[x]=f[x];
}
for(int i=hd[x];i;i=e[i].nxt){
int y=e[i].to;
if(y==fa) continue;
if(1.00-f[y]*e[i].v<=eps){
slo(y,x,e[i].v,0.0);
}
else{
slo(y,x,e[i].v,(1.00-f[x])/(1.00-f[y]*e[i].v));
}
}
}
int main()
{
scanf("%d",&n);
int x,y;int z;
for(int i=1;i<=n-1;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,(double)z/100.0);
add(y,x,(double)z/100.0);
}
for(int i=1;i<=n;i++){
scanf("%d",&z);
p[i]=(double)z/100.0;
}
dfs(1,0);
//cout<<" bug "<<endl;
slo(1,0,0.0,0.0);
for(int i=1;i<=n;i++) op+=ans[i];
printf("%.6lf",op);
return 0;
}
总结:
1.期望与概率和的直接转化。
2.儿子有影响,但是父亲也可能会影响下来,所以就分成两个部分,二次扫描换根
3.注意,存在一个点给x充电的概率,就是(1-不存在一个给x充电的概率)
P=1-(1-p1)(1-p2)...(1-pn)
期望dp+换根,所有点充电的概率之和=答案的期望。
f[i],i子树(包括i)给i充电概率
g[i],i的子树外给i充电概率。
注意,至少有一个充电能充电:P=1-(1-p1)(1-p2)...(1-pn),1-都不能