树形dp/树的遍历
牛客小白月赛45 E筑巢
题目链接:
https://ac.nowcoder.com/acm/contest/11222/E
题意概述:
给一棵n个节点的树,有边权和点权,权值可能为负,要求在树中取一个连通块,使该连通块的权值最大。
解析:
树形dp模板题。用f[i]表示以i为根的子树的权值最大值,可以选择一个连通块意味着在状态转移时,如果i的孩子f[j]为负,则可以不选这个孩子,或者将这个孩子的权值看作是0.
dfs过程:
- 首先将i点的权值设置为f[i]的答案,然后遍历所有的孩子,dfs出孩子的权值f[j],f[i] += max(0, f[j]);
- 注意是一棵树的遍历,所以dfs的时候传参是传u和fa,遍历孩子的时候,如果孩子==father,就continue.
Ac代码
点击查看代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
const int M = 2 * N;
int n;
ll h[N], e[M], ne[M], w[M], idx;
ll f[N], val[N];
void add(ll a, ll b, ll c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a]= idx ++;
}
void dfs(ll u, ll fa){
f[u] = val[u];
for(int i = h[u]; i != -1; i = ne[i]){
ll j = e[i];
if(j == fa) continue;
dfs(j, u);
f[u] += max(0ll, f[j] + w[i]);
}
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%lld", &val[i]);
memset(h, -1, sizeof h);
for(int i = 1; i < n; i ++){
ll x, y, z;
scanf("%lld%lld%lld", &x, &y, &z);
add(x, y, z), add(y, x, z);
}
dfs(1, -1);
ll ans = -1e18;
for(int i = 1; i <= n; i ++) ans = max(ans, f[i]);
printf("%lld\n", ans);
return 0;
}
Acwing 285. 没有上司的舞会
题目链接:
https://www.acwing.com/problem/content/287/
解析
好题。非常典型的树形dp。
-
关于dp的具体过程
首先定义状态表示为f[i][0]和f[i][1],答案为f[root][0]和f[root][1]的最大值。注意树的遍历就和数据结构学的一样,一定是从上往下遍历,所以状态转移是指父节点的状态由子节点转移而来,注意表达式的写法,在转移的时候是一个孩子一个孩子进行转移的,所以更多时候是对答案做 += 运算。 -
关于树的遍历的一些思考
首先,绝大多数时候是不会告诉你树根的,这就决定了建图不可以建单向边。什么时候可以建单向边呢,就是题意是有明确的父节点,给的a, b, w就是只能从a到b,这种情况可以建立由a到b的单向边。通过记录每个节点的父节点,遍历可判断根节点。
Ac代码
点击查看代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 6010;
int h[N], e[N], ne[N], idx;
int a[N], f[N][2], fa[N];
int n;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dfs(int u)
{
f[u][1] = a[u];
for(int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
dfs(j);
f[u][0] += max(f[j][1], f[j][0]);
f[u][1] += f[j][0];
}
return;
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
memset(h, -1, sizeof h);
for(int i = 1; i < n; i ++){
int a, b;
scanf("%d%d", &a, &b);
add(b, a);
fa[a] = b;
}
int root = 0;
for(int i = 1; i <= n; i ++)
if(fa[i] == 0) {
root = i;
break;
}
dfs(root);
printf("%d\n", max(f[root][1], f[root][0]));
return 0;
}
ABC240 E - Ranges on Tree
题目链接
https://atcoder.jp/contests/abc240/tasks/abc240_e
题意
给一个树,让你给树的每个节点一个区间,要求在树中有父子关系的节点,满足子节点的区间是父节点区间的子集;在树中没有父子关系的,满足区间的交集为空。要求区间端点用到的数的个数尽可能少。
解析
就是树的dfs序。给叶子节点标号,左右端点数值相同,所有的父节点由子节点推出。
代码方面要注意的是我是通过记du来判断的叶子节点。因为给的a、b没有说明谁是父节点,所以要正常建双向边,记双向度,然后du为1的是叶子节点,当然根节点可能需要特殊处理一下,如果根节点只有一个儿子的话,会被误判为叶子节点,可以通过建一条-1指向根节点的边解决。
Ac代码
点击查看代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5 + 10;
const int M = 2 * N;
PII ans[N];
int h[N], e[M], ne[M], idx;
int du[N];
bool st[N];
int n;
int val = 0;
void add(int a, int b){
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dfs(int u, int fa)
{
if(du[u] == 1){
st[u] = true;
val ++;
ans[u] = {val, val};
return;
}
int l = 0x3f3f3f3f, r = 0;
for(int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
if(j == fa) continue;
dfs(j, u);
if(st[j]){
l = min(l, ans[j].first);
r = max(r, ans[j].second);
}
}
ans[u] = {l, r};
st[u] = true;
return;
}
int main()
{
scanf("%d", &n);
add(-1, 1);
du[1] ++;
memset(h, -1, sizeof h);
for(int i = 0; i < n - 1; i ++){
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
du[a] ++, du[b] ++;
}
//for(int i = 1; i <= n; i ++) printf("%d ", du[i]);
dfs(1, -1);
for(int i = 1; i <= n; i ++) printf("%d %d\n", ans[i].first, ans[i].second);
return 0;
}
AcWing 1072. 树的最长路径
题目链接
https://www.acwing.com/problem/content/1074/
解析
Ac代码
AcWing 1073. 树的中心
题目链接
https://www.acwing.com/problem/content/1075/
解析
Ac代码
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战