第五部分 动态规划 (第三章 树形DP)例题

本文通过四个例题详细介绍了如何使用树形动态规划解决不同问题:最大权值和、结点覆盖、计算最长距离以及确定最优选课方案。每个例题都给出了清晰的思路和C++实现,展示了树形DP在处理树结构数据时的灵活性和效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

例题一: 树上求和 link

在这里插入图片描述
在这里插入图片描述

思路:
考虑树形DP.
设f[x][0/1]表示第x个节点的子树的最大权值和,0,1表示选或不选这个节点,设y表示x的子节点。
f [ x ] [ 0 ] = m a x ( f [ y ] [ 0 ] , f [ y ] [ 1 ] ) f[x][0] = max(f[y][0], f[y][1]) f[x][0]=max(f[y][0],f[y][1])
f [ x ] [ 1 ] = f [ y ] [ 0 ] + r x f[x][1] = f[y][0] + r_x f[x][1]=f[y][0]+rx

#include <cstdio>
#include <iostream>

using namespace std;

const int N = 6e3 + 10;
struct node {int to, next;} e[N << 1];
int n, r[N], x, y, l, k, head[N], cnt, fa[N], f[N][3];

void add(int x, int y) {e[++cnt] = (node){y, head[x]}, head[x] = cnt;}

void dfs(int x, int fa)
{
	f[x][0] = 0, f[x][1] = r[x];
	for(int i = head[x]; i; i = e[i].next) 
	{
		int v = e[i].to;
		if(v != fa)
		{
			dfs(v, x);
			f[x][0] += max(0, max(f[v][0], f[v][1]));
			f[x][1] += max(0, f[v][0]);
		}
	}
}

int main()
{
	scanf("%d", &n);
	for(int i = 1; i <= n; i++) scanf("%d", &r[i]);
	for(int i = 1; i < n; i++) scanf("%d%d", &l, &k), add(k, l), fa[l] = k;
	for(int i = 1; i <= n; i++)
	{
		if(!fa[i]) 
		{
			dfs(i, 0);
			printf("%d\n", max(f[i][0], f[i][1]));
			return 0;
		}
	}
} 

例题二: 结点覆盖 link

在这里插入图片描述
在这里插入图片描述

思路:
一个点能覆盖自己父亲儿子,
那一个点一定会被它自己或者父亲或者儿子覆盖到。
f i , 0 / 1 / 2 f_{i,0/1/2} fi,0/1/2为搞定i 的子树,
i 是被父亲 / 自己 / 儿子覆盖到的。
然后如果是自己覆盖记得要加上自己的费用,
后面就随意。
然后父亲就是儿子除了选父亲都可以随便。
然后儿子的话就是儿子也是不能选父亲,
而且要至少有一个选自己。
那你就看选儿子变成选自己损失最小的那个,让那个选。
然后这样转移就好啦,
最后输出的就是根节点被自己或儿子占领的最小费用。(根节点没有父亲)

#include <cstdio>
#include <iostream>
#define INF 0x3f3f3f3f

using namespace std;

const int N = 1e3 + 5e2 + 10;
struct node {int to, nxt;} e[N << 1];
int n, a[N], head[N], x, y, z, fa[N], cnt, rt, f[1501][3];

void add(int x, int y) {e[++cnt] = (node){y, head[x]}; head[x] = cnt;}

void dfs(int x, int fa)
{
	f[x][1] = a[x];
	int minn = INF;
	for (int i = head[x]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (v != fa) 
		{
			dfs(v, x);
			f[x][0] += min(f[v][1], f[v][2]);
			f[x][1] += min(f[v][0], min(f[v][1], f[v][2]));
			f[x][2] += min(f[v][1], f[v][2]);
			minn = min(minn, f[v][1] - min(f[v][1], f[v][2]));
		}	
	}
	f[x][2] += minn;
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) 
	{
		scanf("%d", &x);
		scanf("%d", &a[x]);
		scanf("%d", &y);
		while (y--) 
		{
			scanf("%d", &z);
			fa[z] = x; add(x, z);
		}
	}
	for (int i = 1; i <= n; i++)
		if (!fa[i]) 
		{	
			dfs(i, 0);
			printf("%d\n", min(f[i][1], f[i][2]));			
			return 0;
		}
}

例题三: 最长距离 link

在这里插入图片描述

在这里插入图片描述

思路:
考虑当前点到与他最远的节点的路径只有两种:

  1. 一种是往下走
  2. 另一种是先往上走再往下走。

我们设f[i][0/1/2]表示与当前点的最长路径。
0/1/2分别表示向下的最大距离,向下的次大距离,向上的最大距离。

首先考虑向下走的转移,设y表示当前点的子节点:
f [ i ] [ 0 ] = m a x ( f [ y ] [ 0 ] + w e i g h t ( i , y ) ) f[i][0] = max(f[y][0] + weight(i, y)) f[i][0]=max(f[y][0]+weight(i,y))

向下走求次大距离也是这样,注意优先级,
先更新最大值再更新次大值,更新最大值是也要更新次大值。

对于结点x,记与他距离最大的向下结点为index(x),显然:dis[x,index(x)] = weight(x, y);
f [ y ] [ 2 ] = m a x ( f [ x ] [ 1 ] , f [ x ] [ 2 ] ) , y ≠ i n d e x ( x ) f[y][2] = max(f[x][1], f[x][2]),y \neq index(x) f[y][2]=max(f[x][1],f[x][2]),y=index(x)
f [ y ] [ 2 ] = m a x ( f [ x ] [ 0 ] , f [ x ] [ 2 ] ) , y = i n d e x ( x ) f[y][2] = max(f[x][0], f[x][2]),y = index(x) f[y][2]=max(f[x][0],f[x][2]),y=index(x)

对于一个结点i,最后答案为max(f[i][0],f[i][2])

#include <cstdio>
#include <iostream>
#include <cstring>

using namespace std;

typedef long long ll;

const int N = 1e4 + 10;
struct node {ll to, next, val;} e[N << 1];
ll n, x, y, head[N], cnt, f[N][5], pl[N];

void csh()
{
	memset(f, 0, sizeof(f));
	memset(head, 0, sizeof(head));
	cnt = 0;
}

void add(int x, int y, int z) {e[++cnt] = (node){y, head[x], z}, head[x] = cnt;}

void dfs1(ll x, ll fa)
{
	ll maxn = 0, maxx = 0;
	for(ll i = head[x]; i; i = e[i].next)
	{
		ll v = e[i].to;
		if(v == fa) continue;
		dfs1(v, x);
		if(maxn <= f[v][0] + e[i].val) maxx = maxn, maxn = f[v][0] + e[i].val, pl[x] = v; //这里一定要有一个等于
		else if(maxx < f[v][0] + e[i].val) maxx = f[v][0] + e[i].val;
		else if(maxx < f[v][1] + e[i].val) maxx = f[v][1] + e[i].val;
 	}
 	f[x][0] = maxn;
 	f[x][1] = maxx;
}

void dfs2(ll x, ll fa)
{
	for(ll i = head[x]; i; i = e[i].next)
	{
		ll v = e[i].to;
		if(v != fa)
		{
			if(v == pl[x]) f[v][2] = max(f[x][1] + e[i].val, f[x][2] + e[i].val);
			else f[v][2] = max(f[x][0] + e[i].val, f[x][2] + e[i].val);
			dfs2(v, x);
		}
	}
}

int main()
{
	while(scanf("%lld", &n) != EOF)
	{
		csh();
		for(ll i = 2; i <= n; i++)
		{
			scanf("%lld%lld", &x, &y);
			add(x, i, y); 
		}
		dfs1(1, 0), dfs2(1, 0);
		for(ll i = 1; i <= n; i++) printf("%lld\n", max(f[i][0], f[i][2]));	
	}
	return 0;
}

例题四: 选课方案 link

在这里插入图片描述
在这里插入图片描述

思路:
考虑树形 DP。
然后发现范围可以 O ( n 3 ) O(n^3) O(n3)
可以直接放一个背包上去。
f i , j f_{i,j} fi,j为搞定 i 的子树,i 要选,选了 j 个的最大点权和。
然后就从下向上转移,大概就是枚举总共的 j,再枚举这个子树用了多少次。
然后我们发现它是森林,你要合并答案。
然后就会自然的想到用 0 号点把森林变成树,然后要的次数就是 m+1 次,
0 号点的点权是 0,然后即可以了。

#include <cstdio>
#include <iostream>

using namespace std;

const int N = 3e2 + 10;
struct node {int to, next;} e[N << 1];
int n, m, k, s[N], f[N][N], head[N], cnt;

void add(int x, int y) {e[++cnt] = (node){y, head[x]}, head[x] = cnt;}

void dfs(int x, int fa)
{
	for(int i = head[x]; i; i = e[i].next)
	{
		int v = e[i].to;
		if(v == fa) continue;
		dfs(v, x);
		for(int t = m; t > 0; t--)
		 for(int j = t; j >= 0; j--)
		    f[x][t] = max(f[x][t], f[x][t - j] + f[v][j]);
	}
	if(x)
	 for(int t = m; t > 0; t--)
		f[x][t] = f[x][t - 1] + s[x]; 
}

int main()
{
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++) scanf("%d%d", &k, &s[i]), add(k, i);
	dfs(0, 0);
	printf("%d\n", f[0][m]);
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值