首页 > 其他分享 >动态规划5.4-换根树形动态规划

动态规划5.4-换根树形动态规划

时间:2023-10-09 11:45:24浏览次数:51  
标签:子树 5.4 int 路径 节点 为根 动态 换根 size

一、换根树形动态规划

换根树形动态规划又称二次扫描,相较于一般的树形动态规划,有如下特点:

  • 以树上不同的节点为根,其解不同
  • 求解答案时,不能只求解某一点的信息,而是求解所有点的信息
  • 无法通过一次搜索来求解答案

二、例题

1.[Daimayuan Online Judge.距离和]

题目描述

有一棵 \(n\) 个节点的树,节点编号从 \(1\) 到 \(n\)。请求出每个节点到其他所有节点的距离的和。
定义两个节点的距离为它们之间的简单路径上经过了多少条边。

输入格式

第一行一个整数 \(n\) 表示节点数。
接下来 \(n−1\) 行,每行两个整数 \(x,y\) 表示 \(x\) 号节点和 \(y\) 号节点之间有一条边。
数据保证读入的是一棵树。

输出格式

输出共 \(n\) 行,第 \(i\) 行一个整数表示 \(i\) 号节点到其他所有节点的距离的和。

数据范围

对于所有数据,保证 \(2≤n≤10^5,1≤x,y≤n\)。

输入样例
5
1 2
1 5
2 3
2 4
输出样例
6
5
8
8
9
解题思路

考虑以 \(1\) 为根的有根树的情况,用 \(size_i\) 表示以 \(i\) 为根的子树中有多少个点,\(f_i\) 表示考虑以 \(i\) 为根的子树,\(i\) 到子树中其他所有点的距离和。
\(size_i\) 很好计算,考虑 \(j\) 为 \(i\) 的直接子节点,\(size_i=(\sum size_j)+1\)。
\(f_i\) 如何计算?考虑考虑 \(j\) 为 \(i\) 的直接子节点,以 \(j\) 为根的子树对 \(f_i\) 的贡献为 \(f_j+size_j\),则 \(f_i=\sum(f_j+size_j)=\sum f_j+\sum size_j=\sum f_j+size_i-1\)。

image

加上 \(size_j\) 是因为节点 \(j\) 到 \(i\) 的距离为 \(1\),而 \(j\) 子树的每个点到 \(i\) 要经过 \(j\),都需要加 \(1\),恰好为 \(size_j\)。

当根从 \(1\) 变为其子节点 \(i\) 会怎么样?
除了 \(1\) 为根时考虑的子树(贡献值为 \(f_i\)),多了一个以 \(1\) 为根的子树,这个贡献值如何求解,要求解的部分如图所示?

image

用 \(v_i\) 表示一个点的父亲作为其子树时的贡献值,\(v_1=0\),\(v_i=(f_1-f_i-size_i)+(n-size_i)\),即求解 \(v_i\),从总的 \(v_1+f_1\) 中去除以 \(i\) 为子树的贡献值 \(f_i\) 以及相连的那条边的贡献 \(size_i\),然后加上以 \(1\) 为子树的每个点经过 \(1\) 到 \(i\) 的贡献,即以 \(1\) 为根的子树的节点数 \(n-size_i\)。

当根从 \(x\) 变成其子节点 \(y\) 会怎么样?
除了 \(1\) 为根时考虑的子树(贡献值为 \(f_y\)),多了一个以 \(x\) 为根的子树,这个贡献值如何求解?
类似上面的思路,\(v_y=(v_x+f_x-f_y-size_y)+n-size_y\)。首先对于 \(x\) 来说,其总的距离和为 \(v_x+f_x\),而去除以 \(y\) 为根的子树的贡献值以及子树节点经 \(y\) 到 \(x\) 相连的边的贡献值 \(f_y+size_y\) 即可得到以 \(x\) 为根的子树的贡献值,再加上子树节点经 \(x\) 到 \(y\) 相连边的贡献值 \(n-size_y\)。

对于任意的 \(y\),以 \(y\) 为根时的答案为 \(v_y+f_y\)。

C++代码
#include <bits/stdc++.h> 
using namespace std;
const int N = 100010, M = 200010;
typedef long long LL;

int n;
int h[N], e[M], ne[M], idx; 
int size[N];
LL f[N], v[N];
bool b[N];

void add(int x, int y) {
	e[idx] = y, ne[idx] = h[x], h[x] = idx++;
}

void up(int u) {
	size[u] = 1;
	b[u] = true;
	for (int i = h[u]; ~i; i = ne[i]) {
		if (!b[e[i]]) {
			up(e[i]);
			size[u] += size[e[i]];
			f[u] += f[e[i]];
		}
	}
	f[u] += size[u] - 1;
}

void down(int u) {
	b[u] = true;
	for (int i = h[u]; ~i; i = ne[i]) {
		if (!b[e[i]]) {
			v[e[i]] = v[u] + f[u] - f[e[i]] - size[e[i]] + n - size[e[i]];
			down(e[i]);
		}
	}
}

int main() {
	memset(h, -1, sizeof h);
	scanf("%d", &n);
	for (int i = 1; i < n; i++) {
		int x, y;
		scanf("%d%d", &x, &y);
		add(x, y);
		add(y, x);
	}
	memset(b, false, sizeof b);
	up(1);
	memset(b, false, sizeof b);
	down(1);
	for (int i = 1; i <= n; i++)
		printf("%lld\n", f[i] + v[i]);
    return 0;
}

2.[Daimayuan Online Judge.流]

题目描述

有一棵 \(n\) 个节点的树,节点编号从 \(1\) 到 \(n\),树上的每条边都有流量限制。令某一个节点为根节点,向这个节点灌水,最终从叶子节点流出的水量之和为这个节点的最大流量。请求出每个节点的最大流量。

输入格式

第一行一个整数 \(n\) 表示节点数。
接下来 \(n−1\) 行,每行三个整数 \(x,y,z\) 表示 \(x\) 号节点和 \(y\) 号节点之间有一条流量限制为 \(z\) 的边。
数据保证读入的是一棵树。

输出格式

输出共 \(n\) 行,第 \(i\) 行一个整数表示 \(i\) 号节点的最大流量。

数据范围

对于所有数据,保证 \(2≤n≤10^5,1≤x,y≤n,1≤z≤10^9\)。

输入样例
5
1 2 3
1 5 1
2 3 2
2 4 2
输出样例
4
5
2
2
1
解题思路

image
以输入样例为例,以 \(1\) 为根流入,其他点流出的最大流量为 \(1+3=4\),虽然 \(2-3\) 和 \(2-4\) 都为 \(2\) 但是 \(1-2\) 为 \(3\) 会限制流量,其他类似,比如以 \(2\) 为根流入,最后流出的为 \(2+2+1=5\)。

先考虑以 \(1\) 为根(也就是从 \(1\) 灌水)的有根树的情况,\(f_i\) 表示以 \(i\) 为根的子树,最多可以承接多少来自上面的流量,那么 \(f_i\) 如何计算?
假设 \(j\) 是 \(i\) 的儿子,以 \(j\) 为根的子树对 \(f_i\) 的贡献值为 \(min(c_j, f_j)\),其中 \(c_j\) 表示从 \(j\) 到 \(i\) 的边的流量限制,则 \(f_i=\sum min(c_j,f_j)\),另外如果 \(j\) 为叶子节点,其理论上可以流出无限流量,但其会受 \(c_j\) 也就是 \(i\) 到 \(j\) 的边的限制。

当根从 \(1\) 变成它的儿子 \(i\) 时会发生什么?
除了 \(1\) 为根时考虑的子树(贡献值为 \(f_i\)),多了一个以 \(1\) 为根的子树,后面这个贡献值如何求解?
用 \(v_i\) 表示一个点的父亲作为其子树时的贡献(以 \(i\) 的父节点为根的子树最多可以承接的流量),则 \(v_i=min(v_1+f_1-min(c_i,f_i),c_i)\),即首先去除以 \(i\) 为根的子树的流量贡献值即为以 \(1\) 为根的可承接流量,再和 \(c_i\) 取最小值限制。

注意,这里还有一种特殊情况,把根从父亲 \(1\) 号点换成儿子 \(i\) 号点后 \(1\) 号点有可能会成为新的叶子节点,这时候新的以 \(1\) 为根的子树最多可以承接的流量为无限大,所以最多有多少流量可以从 \(i\) 号点流到这颗子树只会被 \(c_i\) 限制,也就是说这时的 \(v_i=c_i\),否则 \(v_1=0\)。

当根从 \(x\) 变成它的儿子 \(y\) 的时候会发生什么?
除了 \(1\) 为根时考虑的子树(贡献值为 \(f_y\)),多了一个以 \(x\) 为根的子树,后面这个贡献值如何求解?
\(v_y=min(v_x+f_x-min(c_y,f_y),c_y)\),对于 \(y\),以其为根时的答案就是 \(v_y+f_y\)。

C++代码
#include <bits/stdc++.h> 
using namespace std;
const int N = 100010, M = 200010;
typedef long long LL;

int n;
int h[N], e[M], w[M], ne[M], idx; 
LL f[N], v[N];
bool b[N];

void add(int x, int y, int z) {
	e[idx] = y, w[idx] = z, ne[idx] = h[x], h[x] = idx++;
}

void up(int u) {
	b[u] = true;
	bool flag = true;
	for (int i = h[u]; ~i; i = ne[i]) {
		if (!b[e[i]]) {
			up(e[i]);
			f[u] += min(1LL * w[i], f[e[i]]);
			flag = false;
		}
	}
	if (flag)
		f[u] = 1 << 30; // 叶子节点不考虑父节点流量为无限大 
}

void down(int u) {
	b[u] = true;
	for (int i = h[u]; ~i; i = ne[i]) {
		if (!b[e[i]]) {
			if (v[u] + f[u] - min(1LL * w[i], f[e[i]]))
				v[e[i]] = min(v[u] + f[u] - min(1LL * w[i], f[e[i]]), 1LL * w[i]);
			else // 所有流量从u到j,则变换根为j,u为叶子节点 
				v[e[i]] = w[i];
			down(e[i]);
		}
	}
}

int main() {
	memset(h, -1, sizeof h);
	scanf("%d", &n);
	for (int i = 1; i < n; i++) {
		int x, y, z;
		scanf("%d%d%d", &x, &y, &z);
		add(x, y, z);
		add(y, x, z);
	}
	memset(b, false, sizeof b);
	up(1);
	memset(b, false, sizeof b);
	down(1);
	for (int i = 1; i <= n; i++)
		if (f[i] != 1 << 30)
			printf("%lld\n", f[i] + v[i]);
		else
			printf("%lld\n", v[i]);
    return 0;
}

3.[Daimayuan Online Judge.最长路径]

题目描述

有一棵 \(n\) 个节点的树,节点编号从 \(1\) 到 \(n\)。对于每个节点,请求出经过它的长度最长的简单路径有多长。
定义一条路径的长度为这条路径上经过了多少条边。

输入格式

第一行一个整数 \(n\) 表示节点数。
接下来 \(n−1\) 行,每行两个整数 \(x,y\) 表示 \(x\) 号节点和 \(y\) 号节点之间有一条边。
数据保证读入的是一棵树。

输出格式

输出共 \(n\) 行,第 \(i\) 行一个整数表示经过 \(i\) 号节点的长度最长的简单路径有多长。

数据范围

对于所有数据,保证 \(2≤n≤10^5,1≤x,y≤n\)。

输入样例
5
1 2
1 5
2 3
2 4
输出样例
3
3
3
3
3
解题思路

基本思路同前两道题。
首先考虑以 \(1\) 为根,则经过 \(1\) 的长度最长的简单路径长度为其到子节点的最长的两个路径之和。那么定义三维数组 \(f\),第一维表示当前节点,第二维表示最长和次长的两个路径,第三维表示路径长度和途径的节点。

这里 \(f\) 的求解可以当做求解以 \(1\) 为根的树维护每个节点两个最大的深度。

随后定义一维数组 \(v\),\(v_i\) 表示 \(i\) 的父节点(以 \(1\) 为根的情况下)维护的路径最大长度。根从 \(1\) 变成其子节点 \(i\),求解 \(v_i\) 需要考虑 \(1\) 维护的最长路径是否经过 \(i\)。然后推广到 \(x\) 的子节点 \(y\)。

答案为每个节点 \(f[i][0][0],f[i][1][0],v[i]\) 三个值中最大的两个值的和。

C++代码
#include <bits/stdc++.h> 
using namespace std;
const int N = 100010, M = 200010;
typedef long long LL;

int n;
int h[N], e[M], ne[M], idx; 
int f[N][2][2], v[N]; // f[i][0]表示最长的路径,f[i][1]表示次长路径,f[i][][0]表示路径长度,f[i][][1]表示经过的子节点 
bool b[N];

void add(int x, int y) {
	e[idx] = y, ne[idx] = h[x], h[x] = idx++;
}

void up(int u) {
	b[u] = true;
	bool flag = true;
	for (int i = h[u]; ~i; i = ne[i]) {
		if (!b[e[i]]) {
			up(e[i]);
			if (f[e[i]][0][0] + 1 > f[u][0][0]) { // 子节点的最长路径+1大于父节点的最长路径
				f[u][1][0] = f[u][0][0]; // 次长路径更新
				f[u][1][1] = f[u][0][1];
				f[u][0][0] = f[e[i]][0][0] + 1;
				f[u][0][1] = e[i]; 
			} else {
				if (f[e[i]][0][0] + 1 > f[u][1][0]) //节点的最长路径+1大于父节点的次长路径
					f[u][1][0] = f[e[i]][0][0] + 1;
					f[u][1][1] = e[i];
			}
		}
	}
}

void down(int u) {
	b[u] = true;
	for (int i = h[u]; ~i; i = ne[i]) {
		if (!b[e[i]]) {
			if (f[u][0][1] == e[i]) { // 父节点最长路径经过当前子节点 
				if (v[u] > f[u][1][0])
					v[e[i]] = v[u] + 1;
				else
					v[e[i]] = f[u][1][0] + 1; 
			} else {
				v[e[i]] = max(f[u][0][0], v[u]) + 1;
			}
			down(e[i]);
		}
	}
}

int main() {
	memset(h, -1, sizeof h);
	scanf("%d", &n);
	for (int i = 1; i < n; i++) {
		int x, y;
		scanf("%d%d", &x, &y);
		add(x, y);
		add(y, x);
	}
	memset(b, false, sizeof b);
	up(1);
	memset(b, false, sizeof b);
	down(1);
	for (int i = 1; i <= n; i++)
		printf("%d\n", f[i][0][0] + f[i][1][0] + v[i] - min(min(f[i][0][0], f[i][1][0]), v[i]));
    return 0;
}

标签:子树,5.4,int,路径,节点,为根,动态,换根,size
From: https://www.cnblogs.com/Cocoicobird/p/17745866.html

相关文章

  • vasp5.4.4+vaspkit安装
    vasp用gnu编译安装是最方便的,下面这个教程非常完整好用vasp-GNU注意看下方评论,第7步更改第33行处,需要删掉-L。vaspkit从sourceforge下载vaspkit打开官网后,右键download获取直链用wget下载即可。解压后运行./setup.sh最后vim~/.vaspkit更改赝势文件路径即可。赝势路径......
  • angular使用from动态设置验证器(clearValidators、setValidators)
    原文链接:https://www.longkui.site/program/frontend/angularfrom/4787/0.背景调试一个angular的form表单,根据条件动态赋予表单的权限验证。主要介绍clearValidators和setValidators的用法。1.代码初始化代码:1234567891011121314151617181920212......
  • uniapp微信小程序设置动态高度为数据量高度
    我的需求是输入信息,然后点击查询按钮,有数据才就调用this.getSvheight函数来设置动态高度,没数据获取到类名的高度为0,我这里做了v-if判断没数据就不渲染这个标签了如果是订单列表那种直接请求列表数据可以在onReady页面进入的时候调用一次,或按照需求调用即可svHeight:0,list:[]......
  • 安卓开发图片动态操作,利用SeekBar控制属性示例
    屏幕大小适配演示,综合练习。功能为,用一个滑块来控制图片的旋转,用一个滑块来控制图片大小,核心语法思路,控制图片的大小,mImageView.setLayoutParams(newLinearLayout.LayoutParams(newWidth,newHeight));但是这儿二个属性要提前配置,并且图片大不能超出屏幕,所以先计算屏幕大小,pr......
  • 动态规划——带权二分优化DP 学习笔记
    动态规划——带权二分优化DP学习笔记引入带权二分其实并不一定用于优化DP,也可能用于优化贪心等最优化的算法。带权二分也叫WQS二分,最初由王钦石在他的2012年国家集训队论文中提出。定义使用情况要解决一个最优化问题(求最大/最小值)有一个限制,一般是某个参数要求一......
  • Maven 引用CDH 5.4 的zookeeper时报错:Could not find artifact javax.jms:jms:jar:1.1
    错误:Couldnotfindartifactjavax.jms:jms:jar:1.1incloudera由于默认5.4.0的包引用了zookeeper3.3.1版本,进而引用了log4j的某个版本,导致的报错,改为如下即可: pom:使用cloudera的源:<repositories><repository><id>cloudera</id><u......
  • 如何利用动态配置中心在JavaAgent中实现微服务的多样化治理
    本文分享自华为云社区《如何利用动态配置中心在JavaAgent中实现微服务的多样化治理》,作者:华为云开源。  一、前言随着JavaAgent在微服务治理方面的广泛应用和发展,我们可以在运行时对微服务进行监控、管理和调整,以满足不同的业务需求和运行环境。然而,随着微服务架构的复杂性增加,......
  • 服务器动态下线
     ######haproxy动态下线需要用到socat工具socat工具:对服务器动态权重和其它状态可以利用socat工具进行调整,Socat是Linux下的一个多功能的网络工具,名字来由是SocketCAT,相当于netCAT的增强版.Socat的主要特点就是在两个数据流之间建立双向通道,且支持众多协......
  • openstack cinder实现基于lvm、NFS实现云盘动态拉伸
     #cindder部署官方参考文档https://docs.openstack.org/cinder/train/install/cinder-controller-install-rdo.html1.#数据库准备:[root@openstack-mysql~]#mysqlWelcometotheMariaDBmonitor.Commandsendwith;or\g.YourMariaDBconnectioni......
  • 【实用】登录图形认证 图形码 验证码 中文图形验证码 动态图形验证码 图片验证码 验证
    后端测试: 主要code:https://www.cnblogs.com/liuguiqing/p/17722366.html ......