树形DP整理小结

这篇博客总结了树形动态规划(DP)的应用,通过分析POJ和HDU等竞赛题目,阐述如何处理非完整树结构的问题。博主建议将森林转化为一棵树,并设定虚拟根节点的无穷大成本来解决问题。讨论了dp[node][i]表示以node为根的子树获得i张选票的最小花费,以及在处理不同儿子节点时与背包问题的相似性和区别。还提醒了处理输入的注意事项。

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

树形DP:

在树上进行dp,树是递归的定义的,所以树形dp也是递归的求解,一般而言,dp[node]表示的是以node为根的子树能得到的最优解。

一般而言,dp[node]需要从node的子结点进行状态转移,

树形dp又常常和背包结合起来,因为dp[node]的状态是由它的儿子转移而来。

我们常常可以将node的n个儿子看做n个物品,要如何对这n个物品抉择得到最优的dp[node]就常用到背包的思想。

当然,常常dp不止node这一维,至于第二维保存什么 则根据题目来,

一般都是题目给出的限制条件作为dp数组的第二维,然后根据题意保存好状态,写出状态转移方程,

然后递归的求解dp[root]

还有,树形DP还涉及到建图的问题,如果题目能很清晰的输入一个树最好

用vector<int>s[N],s[i]保存节点i的所有儿子

而如果没有的话,保存之后的s会出现j是i的儿子同时i也是j的儿子的情况,这样的情况只需要在递归的过程中

对于当前节点的父亲做标记,枚举其儿子的时候跳过父亲即可(例如下面的第1题)

Problems:

前五题都是二维的dp,需要想好状态转移方程,且大多是树上的背包问题
后五题都是一维的dp,在树上删点或者删边的问题,相对而言后五题更简单
不过第10题给出的图不是树,需要tarjan缩点处理成一棵树

1.POJ 2486 Apple Tree

dp[i][j][0]表示从i节点出发最后回到i节点花费最多j步能获得的最大值, 
dp[i][j][1]表示从j节点出发最后不回到i节点花费最多j步能获得的最大值 
dp[root][j][0] = max(dp[root][j-k][0] + dp[son][k-2][0])
//root->son 和 son->root 共花费2步,j-k是在其他儿子花费的步数
dp[root][j][1] = max(dp[root][j-k][0] + dp[son][k-1][1])
dp[root][j][1] = max(dp[root][j-k][1] + dp[son][k-2][0])
//只需要回到root一次,一种情况是先走其他儿子回到root再走son,
//另一种情况是先走son这个儿子并回到root再去走其他儿子 

#define mem(a,x) memset(a,x,sizeof(a))
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<set>
#include<stack>
#include<cmath>
#include<map>
#include<stdlib.h>
#include<cctype>
#include<string>
#define Sint(n) scanf("%d",&n)
#define Sll(n) scanf("%I64d",&n)
#define Schar(n) scanf("%c",&n)
#define Sint2(x,y) scanf("%d %d",&x,&y)
#define Sll2(x,y) scanf("%I64d %I64d",&x,&y)
#define Pint(x) printf("%d",x)
#define Pllc(x,c) printf("%I64d%c",x,c)
#define Pintc(x,c) printf("%d%c",x,c)
using namespace std;
typedef long long ll;
int dp[111][222][2];
/*
	dp[i][j][0]表示从i节点出发最后回到i节点花费最多j步能获得的最大值, 
	dp[i][j][1]表示从j节点出发最后不回到i节点花费最多j步能获得的最大值 
	
	dp[root][j][0] = max(dp[root][j-k][0] + dp[son][k-2][0])
	//root->son 和 son->root 共花费2步,j-k是在其他儿子花费的步数
	
	dp[root][j][1] = max(dp[root][j-k][0] + dp[son][k-1][1])
	dp[root][j][1] = max(dp[root][j-k][1] + dp[son][k-2][0])
	//只需要回到root一次,一种情况是先走其他儿子回到root再走son,
	//另一种情况是先走son这个儿子并回到root再去走其他儿子 
*/
vector<int>s[111];
int k;
void treedp(int node,int fa)
{
	for (int i = 0;i < s[node].size();++i)//从节点node出发 
	{
		int &son = s[node][i];
		if (son == fa) continue;
		treedp(son,node);
		for (int j = k;j >= 0;--j)//枚举node节点所有可能花费的步数 
		{
			for (int ot = 0;ot <= j;++ot)//枚举在son这个儿子上花费的步数
			{
				if (ot-2>=0) dp[node][j][0] = max(dp[node][j][0],dp[node][j-ot][0]+dp[son][ot-2][0]);
				if (ot-1>=0) dp[node][j][1] = max(dp[node][j][1],dp[node][j-ot][0]+dp[son][ot-1][1]);
				if (ot-2>=0) dp[node][j][1] = max(dp[node][j][1],dp[node][j-ot][1]+dp[son][ot-2][0]);
			} 
		} 
	}
} 
int main()
{
    int n;
    while (Sint2(n,k) == 2)
    {
    	mem(dp,0);mem(s,0);
    	for (int i = 1,v;i <= n;++i)
    	{
    		Sint(v);
			for (int j = 0;j <= k;++j)
			{
				dp[i][j][0] = dp[i][j][1] = v;
			} 
		}
		for (int i = 1,x,y;i < n;++i)
		{
			Sint2(x,y);
			s[x].push_back(y);
			s[y].push_back(x);
		}
		treedp(1,0);/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值