【换根DP】【期望DP】概率充电器(SHTSC2014)

链接

luogu P4284

题目描述

给出一颗无根树,每个点有一定概率充电,充上电的点可以通过边来给别的点充电,边有一定概率通电,现在要求这棵树的通电点数的期望

样例输入

3

1 2 50

1 3 50

50 0 0

样例输出

1.000000

思路

好像直接求不是很好求
那么我们把求总的点数的期望改成求一个点通电的概率,然后再加起来就是答案了
那么一个点怎么求通电概率呢
首先想到的是设 f i f_i fi为i点通电的总概率,但是这样子会有很多的式子要处理,那么我们就考虑设 f i f_i fi为该点不通电的概率
很容易想到转移就是
f i = ( 1 − p i ) ∗ ( 1 − g i , v + g i , v ∗ f v ) f_i = (1-p_i)*(1-g_{i,v}+g_{i,v}*f_ v) fi=(1pi)(1gi,v+gi,vfv)
解释一下,首先是它自身不通电 ( 1 − p i ) (1-p_i) (1pi),然后是它连的边不通电 1 − g i , v 1-g_{i,v} 1gi,v.再然后就是边通电,那么它连的点就不可以通电 ( g i , v ∗ f v ) (g_{i,v}*f_ v) (gi,vfv)
这个可以通过设1点为根去求
那么就可以得到1点的正确答案
但是,想到原图其实是一颗无根树
那么每个点都有可能为根,我们就需要考虑别的点为根的情况
很显然,一个点从儿子变为根,它会多出原本它的父亲的贡献
在这里插入图片描述
红色部分,在第一次以1为根时求出的,在以u为根的情况下就成为了u的贡献
那么设 g i g_i gi为以i为根时的最终答案
额外贡献很显然就是它原父亲的贡献/它自己的贡献(因为是乘法)
q = g f a ( 1 − w f a , i + w f a , i ∗ f i ) q = \dfrac{g_{fa}}{(1-w_{fa,i} + w_{fa,i}*f_i)} q=(1wfa,i+wfa,ifi)gfa
g i = f i ∗ ( 1 − w f a , i + w f a , i ∗ q ) g_i = f_i * (1-w_{fa,i}+w_{fa,i} * q) gi=fi(1wfa,i+wfa,iq)
这个换根DP可以用两次搜索实现,第一次以1为根,第二次就不断换根
但是jzoj很恶心地用dfs会爆栈,所以我用的bfs打(很丑勿喷)

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>

using namespace std;

int n, tot, t, h[500005];
double q[500005], f[500005], g[500005], ans;

struct lq
{
	int a, b, c;
}st[500005];

struct node
{
	int to, next;
	double val;
}w[1000005];

struct qq
{
	int now, fa;
};

void bfsl(int x, int fa)
{
	st[++tot] = (lq){x, fa, 0};
	//第三个位置这里,0表示还未处理儿子,1表示儿子处理完
	//当儿子处理完了向上转移才是正确的
	while(tot)
	{
		int now = st[tot].a;
		int father = st[tot].b;
		int pd = st[tot--].c;
		if(!pd)
		{
			st[++tot] = (lq){now, father, 1};//定为1,然后处理儿子(因为是栈,所以一定先处理后面加入的)
			f[now] = 1.0 - q[now];
			for(int i = h[now]; i; i = w[i].next)
			{
				int to = w[i].to;
				if(to == father) continue;
				st[++tot] = (lq){to, now, 0};
			}
		}	
		else {
			for(int i = h[now]; i; i = w[i].next)
			{
				int to = w[i].to;
				if(to == father) continue;
				f[now] *= (1.0 - w[i].val + w[i].val * f[to]);
			}
		}
	}	
}

void bfsq(int x, int fa)
{
	queue<qq>Q;
	Q.push((qq){x, fa});
	while(Q.size())
	{
		int now = Q.front().now;
		int father = Q.front().fa;
		Q.pop();
		ans += g[now];
		for(int i = h[now]; i; i = w[i].next)
		{
			int to = w[i].to;
			if(to == father) continue;
			double rest = g[now] / (1 - w[i].val + w[i].val * f[to]);
			g[to] = f[to] * (1 - w[i].val + w[i].val * rest);
			Q.push((qq){to, now});
		}
	}
}//换根DP

int main()
{
//	freopen("charger.in", "r", stdin);
//	freopen("charger.out", "w", stdout);
	scanf("%d", &n);
	for(int i = 1; i < n; ++i)
	{
		int u, v, p;
		double pp;
		scanf("%d%d%d", &u, &v, &p);
		pp = 1.0 * p / 100.0;
		w[++t] = (node){v, h[u], pp}; h[u] = t; 
		w[++t] = (node){u, h[v], pp}; h[v] = t;
	}
	for(int i = 1; i <= n; ++i) {
		int p;
		scanf("%d", &p);
		q[i] = 1.0 * p / 100.0;
	}
	bfsl(1, 0);//以1为根
	g[1] = f[1];
	bfsq(1, 0);//换根
	printf("%.6lf", 1.0 * n - ans);//ans是每个点不发光的概率,用总数减去就是答案了
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值