BZOJ3566: [SHOI2014]概率充电器

这题要算期望,个数的期望

容易发现每一个点对期望的贡献就是它被接通的概率

之后考虑 dp

在玩方程的时候会发现这样一个事情:如果状态定义为被接通的概率的话,在转移时要加子树的然后再除个总概率什么的,总之很麻烦

但是可以考虑一下计算不被接通的概率,这样计算一个 pi 就好了

之后容易想到二次扫描换根

转移子树的方程是 : d[x] = (1 - p[x]) * ∏[d[son[i]] * edge[i].val + 1 - edge[i].val]

转移以当前点为根的方程是 : f[x] = d[x] * {(1 - edge[i].val) +edge[i].val * [f[fa] / (d[x] *edge[i].val + 1 - edge[i].val)]}

需要注意的是这样定义状态会导致转移方程中出现除,并且这个除数是一个带减号的式子,所以可能会除 0,判掉就好


代码:

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cstdio>
using namespace std;

const int MAXN = 500005;
const double eps = 1e-6;

struct EDGE{
	int nxt, to;
	double val;
	EDGE(int NXT = 0, int TO = 0, double VAL = 0.0) {nxt = NXT; to = TO; val = VAL;}
}edge[MAXN << 1];
int n, totedge;
int head[MAXN];
double d[MAXN], f[MAXN], p[MAXN];
double ans;

inline void add(int x, int y, double v) {
	edge[++totedge] = EDGE(head[x], y, v);
	head[x] = totedge;
	return;
}
void dfs(int x, int fa) {
	d[x] = 1.0 - p[x];
	for(int i = head[x]; i; i = edge[i].nxt) if(edge[i].to != fa) {
		int y = edge[i].to;
		dfs(y, x);
		d[x] *= ((1.0 - edge[i].val) + (edge[i].val * d[y]));
	}
	return;
}
void efs(int x, int fa) {
	for(int i = head[x]; i; i = edge[i].nxt) if(edge[i].to != fa) {
		int y = edge[i].to;
		double k = (d[y] * edge[i].val + 1.0 - edge[i].val);
		if(k > eps) f[y] = d[y] * ((1.0 - edge[i].val) + edge[i].val * f[x] / k);
		efs(y, x);
	}
	return;
}

int main() {
	scanf("%d", &n);
	register int xx, yy;
	register double vv;
	for(int i = 1; i < n; ++i) {
		scanf("%d%d%lf", &xx, &yy, &vv);
		vv /= 100.0;
		add(xx, yy, vv); add(yy, xx, vv);
	}
	for(int i = 1; i <= n; ++i) {
		scanf("%lf", &p[i]);
		p[i] /= 100.0;
	}
	dfs(1, 0);
	f[1] = d[1];
	efs(1, 0);
	for(int i = 1; i <= n; ++i) ans += 1.0 - f[i];
	printf("%lf\n", ans);
	return 0;
}

 

posted @ 2018-09-17 09:53  EvalonXing  阅读(154)  评论(0编辑  收藏  举报