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


    随机游走 / T1

    题目大意

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

    思路

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

    考虑从这个作为最初始的状态,然后 DP。((d_i) 是点 (i) 的度数)
    然后就考虑求出以 (1) 为根的时候,每个点到它父亲的期望步数。
    然后可以得出:(f_i=dfrac{1}{d_i}+sumlimits_{j=son_i}dfrac{1+f_j+f_i}{d_i})
    然后通过移项可以得到 (f_i=d_i+sumlimits_{j=son_i}f_j)

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

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

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

    考虑枚举你选的两个点的 LCA。
    那我们可以预处理 DP 求出 (a_i,b_i),分别代表 (i) 到它子树里面的点的最长期望距离和它子树里面的点到它的最长期望距离。
    然后接着对于每个点,我们把它的每个儿子的 (a,b) 值都拿出来。
    然后枚举儿子,就有 (f_j=b_j+f_{j,i}+maxlimits_{k=son_i,k eq j}{a_k+f_{i,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;
    }
    
  • 相关阅读:
    导航控制器生产,push,pop,root,index
    DNSserver内置笔记本
    解决“Dynamic Web Module 3.0 requires Java 1.6 or newer.”错误
    ssh配置连接
    在UITouch事件中画圆圈-iOS8 Swift基础教程
    iOS之UITableViewCell左右滑动效果
    iOS UIView非常用方法及属性详解
    IOS用CGContextRef画各种图形(文字、圆、直线、弧线、矩形、扇形、椭圆、三角形、圆角矩形、贝塞尔曲线、图片)
    UIColor,CGColor,CIColor三者的区别和联系
    iOS中正确的截屏姿势
  • 原文地址:https://www.cnblogs.com/Sakura-TJH/p/jzoj_4890.html
Copyright © 2020-2023  润新知