【jzoj 3661】【luogu P4284】Charger / 概率充电器(期望DP)(换根DP)

168 篇文章 0 订阅
24 篇文章 2 订阅

Charger / 概率充电器

题目链接:jzoj 3661 / luogu P4284

题目大意

有一些点,一个点有亮不亮两种可能。
一个点本身有一定的概率会亮,然后点之间有边,把点连接构成了一棵树。
然后每条边有一定概率,使得如果两边有一个点亮了,另一边也会亮。
问你亮的点的期望个数。

思路

你可以求出每个点亮的概率,然后加起来就是期望。
你考虑求一个 x x x 点亮的概率,发现可以 DP 求,设 f i f_i fi i i i 亮的概率。(只考虑以 x x x 为根它的子树)
然后就 DP, f i = q i ∏ j = s o n i f j p i , j f_i=q_i\prod\limits_{j=son_i}f_jp_{i,j} fi=qij=sonifjpi,j

然后复杂度是 O ( n 2 ) O(n^2) O(n2),然后你考虑换根。
然后你发现这么设换根不太好搞,我们考虑设不亮的概率。
f i = ( 1 − q i ) ∗ ∏ j = s o n i ( 1.0 − p i , j + p i , j f j ) f_i=(1-q_i)*\prod\limits_{j=son_i}(1.0-p_{i,j}+p_{i,j}f_{j}) fi=(1qi)j=soni(1.0pi,j+pi,jfj)
然后你继续考虑换根,你发现新的答案其实就是你旧的答案减去你这个点子树的贡献再作为你这个点的子树贡献上来。
减去这个子树的贡献其实就是除以右边的那个。
(具体可以看看代码)

然后搞出来就好了。

然后因为 jzoj 用 dfs 会爆栈 RE,所以我自己写了个比较丑的 bfs。

代码

#include<queue>
#include<cstdio>

using namespace std;

struct node {
	double x;
	int to, nxt;
}e[2000001];
int n, x, y, z, le[1000001], KK;
double p[1000001], f[1000001], ans, g[1000001];
int st1[10000001], st2[10000001], st3[10000001];

void add(int x, int y, double z) {
	e[++KK] = (node){z, y, le[x]}; le[x] = KK;
	e[++KK] = (node){z, x, le[y]}; le[y] = KK;
}

//求出一个为根的结果
void bfs(int now, int father) {
	st1[++st1[0]] = now;
	st2[++st2[0]] = father;
	st3[++st3[0]] = 0;
	int op;
	while (st1[0]) {
		now = st1[st1[0]--];
		father = st2[st2[0]--];
		op = st3[st3[0]--];
		if (!op) {
			st1[++st1[0]] = now;
			st2[++st2[0]] = father;
			st3[++st3[0]] = 1;
			f[now] = 1.0 - p[now];
			for (int i = le[now]; i; i = e[i].nxt)
				if (e[i].to != father) {
					st1[++st1[0]] = e[i].to;
					st2[++st2[0]] = now;
					st3[++st3[0]] = 0;
//					q1.push(e[i].to); q2.push(now); q3.push(0);
//					f[now] *= (1.0 - e[i].x + e[i].x * f[e[i].to]);
				}
//			q1.push(now); q2.push(father); q3.push(1);
		}
		else {
			for (int i = le[now]; i; i = e[i].nxt)
				if (e[i].to != father) {
					f[now] *= (1.0 - e[i].x + e[i].x * f[e[i].to]);
				}
		}
	}
}

//换根DP
void bfsDio(int now, int father) {
	queue <int> q1, q2;
	q1.push(now); q2.push(father);
	while (!q1.empty()) {
		now = q1.front(); q1.pop();
		father = q2.front(); q2.pop();
		ans += g[now];
		for (int i = le[now]; i; i = e[i].nxt)
			if (e[i].to != father) {
				double el = g[now] / (1.0 - e[i].x + e[i].x * f[e[i].to]);
				g[e[i].to] = f[e[i].to] * (1.0 - e[i].x + e[i].x * el);
				q1.push(e[i].to); q2.push(now);
			}
	}
}

int main() {
//	freopen("charger.in", "r", stdin);
//	freopen("charger.out", "w", stdout);
	
	scanf("%d", &n);
	for (int i = 1; i < n; i++) {
		scanf("%d %d %d", &x, &y, &z);
		add(x, y, 1.0 * z / 100.0);
	}
	for (int i = 1; i <= n; i++) {
		scanf("%d", &x);
		f[i] = p[i] = 1.0 * x / 100.0;
	}
	
	bfs(1, 0);
	g[1] = f[1];
	bfsDio(1, 0);
	
	printf("%.6lf", 1.0 * n - ans);
	
	fclose(stdin);
	fclose(stdout);
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值