最近几天一直在看树形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

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

被折叠的 条评论
为什么被折叠?



