C++树形dp经验总结(换根dp,点覆盖问题,含例题City Upgrading)

文章介绍了树形动态规划(树形dp)的概念,重点讨论了两种常见问题类型:换根dp和点覆盖问题。换根dp常用于求解以哪个节点为根时树的某种属性最大,而点覆盖问题则是寻找最少节点数以覆盖树的所有边。文章提供了相应的代码示例和解题思路。

最近几天一直在看树形dp的相关问题,并写了几道经典的模板例题,这里来稍微总结一下树形dp。

首先顾名思义,树形dp就是指题意要求,在树形的结构上的动态规划问题,转移方程通常就是从树的父节点和子节点之间进行转移,所以核心的状态转移部分是在dfs深度搜索中实现,下面是两个常见的树形dp问题类型。

一是换根dp,这个说是树形dp的类型,倒不如说是树形dp的具体方法的一种,通常见于求树上某个结点的什么属性要最大,然后这个属性又需要我们从这个结点出发遍历一次树才能得到。从暴力的角度思考,枚举每个结点并搜索一次,复杂度就是O(n²),一般是会超时的(如果题目是想你用树形dp做的话),这时候我们就得考虑用树形dp,也就是如何用一次搜索的结果,来得到每个结点的对应答案。

经典的例题比如STA-Station,求一棵树中,以哪个结点作为根,其他所有结点的深度之和最大。单独一个点的深度之和用dfs或者bfs一下就很容易可以得到,但是想要求以哪个结点为根得到的答案最大,没法枚举每一个结点都跑一遍搜索。但是我们可以通过记录结点的一些信息,来递推得到这个结果。需要记录的信息包括,以初始的某个结点为根跑完dfs后,每个结点的深度,每个结点的子树的大小(包括本身的结点个数),以及以该结点为根时的其他结点深度和。具体的推导过程就不放这了,大概可以自己试着推推看,核心利用根从父节点变为子节点时,原先子节点的子树的深度统统减一,父节点的其他子树的深度加一,利用这个关系可以得到递推式。然后再跑一遍dfs,从父节点开始向子节点不断更新答案数组。下面放放我的代码:

#include<bits/stdc++.h>
#include<queue>
#define db double
#define ll long long
#define ui unsigned int
using namespace std;
const int maxn = 1e6+10;
vector<int>G[maxn];
ll dep[maxn],size[maxn],f[maxn],n;
int read() {
	int x = 0, f = 0, ch = getchar();
	while (!isdigit(ch)) { if (ch == '-')f = 1;ch = getchar(); }
	while (isdigit(ch)) { x = x * 10 + ch - '0';ch = getchar(); }
	return f ? -x : x;
}
void dfs1(int u,int fa){
	for(auto v:G[u]){
		if(v==fa)continue;
		dep[v]=dep[u]+1;
		dfs1(v,u);
		size[u]+=size[v];
		f[u]+=f[v];
	}
	size[u]++;
	f[u]+=dep[u];
}
void dfs2(int u,int fa){
	for(auto v:G[u]){
		if(v==fa)continue;
		f[v]=n-2*size[v]+f[u];
		dfs2(v,u);
	}
}
in
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值