随机游走 / T1(期望)(树形DP)

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

随机游走 / T1

题目大意

给你一个树,问你对于每个点对 (i,j),i 走到 j 的期望步数的最大值。
行走的方式是在可以一步到达的点中等概率的选择一个走过去。

思路

首先我们观察样例,发现如果一个点是叶子节点(或者说度数是 1 1 1),那它到它父亲(那条边连到的点)的期望步数一定是 1 1 1

考虑从这个作为最初始的状态,然后 DP。( d i d_i di 是点 i i i 的度数)
然后就考虑求出以 1 1 1 为根的时候,每个点到它父亲的期望步数。
然后可以得出: f i = 1 d i + ∑ j = s o n i 1 + f j + f i d i f_i=\dfrac{1}{d_i}+\sum\limits_{j=son_i}\dfrac{1+f_j+f_i}{d_i} fi=di1+j=sonidi1+fj+fi
然后通过移项可以得到 f i = d i + ∑ j = s o n i f j f_i=d_i+\sum\limits_{j=son_i}f_j fi=di+j=sonifj

然后你就可以 O ( n ) O(n) O(n) 求,然后我们考虑求从父亲到它的。
那你考虑 i i i 到它父亲和它父亲到 i i i 的期望步数之间的关系。

那它父亲到它套进前面的方程,就是 ∑ j 在 i 的 子 树 内 { d j } \sum\limits_{j在i的子树内}\{d_j\} ji{dj}
然后你考虑每个 d i d_i di 被计算多少次,会发现从它到父亲,除了它到它父亲的边值计算了一次,下面的边都是计算了两次。
然后父亲到它也是同一个道理。
那不难想到总次数分别是 2 ∗ s z i − 1 , 2 ∗ ( n − s z i ) − 1 2*sz_i-1,2*(n-sz_i)-1 2szi1,2(nszi)1,那加在一起,就变成了 2 ∗ n − 2 2*n-2 2n2 2 ∗ ( n − 1 ) 2*(n-1) 2(n1)
所以你就可以通过让 2 ∗ ( n − 1 ) 2*(n-1) 2(n1) 减去它到父亲的期望步数,得到父亲到它的期望步数。

自此, n 2 n^2 n2 的做法已经可以了。
那我们考虑 O ( n ) O(n) O(n)

考虑枚举你选的两个点的 LCA。
那我们可以预处理 DP 求出 a i , b i a_i,b_i ai,bi,分别代表 i i i 到它子树里面的点的最长期望距离和它子树里面的点到它的最长期望距离。
然后接着对于每个点,我们把它的每个儿子的 a , b a,b a,b 值都拿出来。
然后枚举儿子,就有 f j = b j + f j , i + max ⁡ k = s o n i , k ≠ j { a k + f i , k } f_j=b_j+f_{j,i}+\max\limits_{k=son_i,k\neq j}\{a_k+f_{i,k}\} fj=bj+fj,i+k=soni,k=jmax{ak+fi,k}
然后这个维护一下前缀后缀最大值即可。

然后就可以了。

代码

#include<cstdio>
#include<iostream>

using namespace std;

struct node {
	int to, nxt;
}e[200001];
int n, x, y, le[100001], KK, tmpn;
int fa[100001], du[100001], pl[100001];
double ans, f[100001], a[100001], b[100001];
double tmp[100001], q[100002], h[100002];

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

void dfs1(int now, int father) {//第一个 dfs 求出 i 走到 fai 的期望步数
	f[now] = du[now];
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].to != father) {
			dfs1(e[i].to, now);
			f[now] += f[e[i].to];
		}
}

void dfsab(int now, int father) {//得出转移用的 ab 数组
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].to != father) {
			dfsab(e[i].to, now);
			a[now] = max(a[now], a[e[i].to] + 2 * (n - 1) - f[e[i].to]);
			b[now] = max(b[now], b[e[i].to] + f[e[i].to]);
		}
}

void dfsans(int now, int father) {//对于每个 LCA 求值
	tmpn = 0;
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].to != father) {
			tmp[++tmpn] = a[e[i].to] + 2 * (n - 1) - f[e[i].to];
			pl[tmpn] = e[i].to;
		}
	ans = max(ans, max(a[now], b[now]));
	for (int i = 1; i <= tmpn; i++)
		q[i] = max(q[i - 1], tmp[i]);
	for (int i = tmpn; i >= 1; i--)
		h[i] = max(q[i + 1], tmp[i]);
	for (int i = 1; i <= tmpn; i++)
		ans = max(ans, b[pl[i]] + f[pl[i]] + max(q[i - 1], h[i + 1]));
	
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].to != father) {
			dfsans(e[i].to, now);
		}
}

int main() {
//	freopen("rw.in", "r", stdin);
//	freopen("rw.out", "w", stdout);
	
	scanf("%d", &n);
	for (int i = 1; i < n; i++) {
		scanf("%d %d", &x, &y);
		add(x, y); du[x]++; du[y]++;
	}
	
	dfs1(1, 0);
	dfsab(1, 0);
	dfsans(1, 0);
	
	printf("%.5lf", ans);
	
	fclose(stdin);
	fclose(stdout);
	
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值