树形DP——POJ1947 and POJ2486

本文介绍了如何使用树形动态规划(DP)解决POJ1947和POJ2486两道题目。在POJ1947中,状态dp[i][p]表示以节点i为根的树,持有p个节点需要摧毁的最少公路数量,通过状态转移方程进行求解。而在POJ2486中,dp[i][k][0]和dp[i][k][1]分别表示女主角在有限步数内返回节点i或停留在子树中能吃到的最大苹果数,通过三种状态转移方程进行优化计算。

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

本文针对POJ上的2057 、1947 、2486 、3140 四道题目,谈一下个人对树形DP的理解,上面4题无法覆盖树形DP的所有形式,但也有一定的针对性,值得去做一做,思考一下:
树形DP中常常用到“背包思想”,1947和2486就是利用了“背包思想”,状态想好了,最后就是在填“背包”,某个节点的“背包”是用他们的子树的状态进行填充的。
  • POJ1947:状态dp[i][p]表示以节点i为根的树,持有p个节点需要摧毁的最少的公路,状态转移方程大体可以写为dp[i][p]=Min{dp[i][p],{dp[i][p-k]+dp[j][k]|j is a child of i}}
  • POJ2486:状态dp[i][k][0]表示有k步可以走的情况下,女主角回到i节点吃到的最大苹果数;dp[i][k][1]表示有k步可以周的情况下,女主角不一定回到i(可能在i的子树中停止)吃到的最大苹果数。状态转移方程大体可以写为三个(1)dp[i][k][0]=Max{dp[i][k][0],{dp[i][k-l-2][0]+dp[j][l][0]|j is a child of i}};(2)dp[i][k][1]=Max{dp[i][k][1],{dp[i][k-l-2][0]+dp[j][l][1]|j is a child of i}};(3)dp[i][k][1]=Max{dp[i][k][1],{dp[i][k-l-2][1]+dp[j][l][0]|j is a child of i}};
至于POJ2057,首先要将子树进行排序,然后计算子树的一个状态——知道Home不在此子树中所要走的最少的步数
至于POJ3140,就是一个水题

POJ1947 codes
#include<iostream>
#include<string.h>
using namespace std;

#define SIZE 152

int dp[SIZE][SIZE];
int children[SIZE][SIZE];
int parent[SIZE];
int col[SIZE];
int P;

void dfs(int now){
	if(children[now][0]==0){
		dp[now][1]=0;
		return ;
	}
	dp[now][1]=0;
	for(int i=1;i<=children[now][0];i++){
		dfs(children[now][i]);
		for(int j=P;j>=1;j--){
			if(dp[now][j]!=-1){
			   for(int k =1;k<=P;k++)
				   if(dp[children[now][i]][k]!=-1 && j+k<=P &&
					   (dp[now][j+k]==-1 || dp[children[now][i]][k]+dp[now][j] < dp[now][j+k])){
				       dp[now][j+k] = dp[children[now][i]][k]+dp[now][j];
				   }
				dp[now][j]+=1;
			}
		}
	}
}

int main()
{
	int N;
	scanf("%d%d",&N,&P);

	memset(dp,-1,sizeof(dp));
	memset(children,0,sizeof(children));
	memset(parent,-1,sizeof(parent));
	memset(col,-1,sizeof(col));

	for(int i=1;i<N;i++){
		int p,c;
		scanf("%d%d",&p,&c);
		parent[c]=p;
		children[p][++children[p][0]] = c;
	}

	int root = 0;

	for(int i=1;i<=N;i++)
		if(parent[i]==-1){
			root = i;
			break;
		}

	dfs(root);

	int res = -1;
	for(int i=1;i<=N;i++)
		if(dp[i][P]!=-1){
		   int tmp = 0;
		   if(parent[i]==-1) tmp = dp[i][P];
		   else tmp = dp[i][P]+1;
		   if(res==-1 || tmp < res)
			   res = tmp;
		}

	printf("%d\n",res);

	system("pause");
}

POJ2486 codes
/*
	dp[i][j][0]:在i节点还可以走j步,又走回i节点得到的最大苹果数
	dp[i][j][1]:在i节点还可以走j步,不一定走回i节点得到的最大苹果数
	
	dp[i][j][0]的转移方程很简单,dp[i][j][1]的转移方程有两个,注意体会第二
*/
#include<iostream>
#include<string.h>
using namespace std;

#define SIZE 105

int children[SIZE][SIZE];
int val[SIZE];
int dp[SIZE][205][2];

int N,K;

#define _max(x,y) ((x)>(y)?(x):(y))

void dfs(int now,int parent){

	for(int i=0;i<=K;i++)
		dp[now][i][0]=dp[now][i][1]=val[now];

	if(children[now][0]!=0){
		for(int i=1;i<=children[now][0];i++){
			int child = children[now][i];
			if(child == parent) continue;
			dfs(child,now);

			for(int k=K;k>=0;k--)
				for(int j=0;j<=k;j++){
					//dp[now][k+2][0]
					dp[now][k+2][0] = _max(dp[now][k+2][0],dp[now][k-j][0]+dp[child][j][0]);
					//dp[now][k+1][1]
					dp[now][k+1][1] = _max(dp[now][k+1][1],dp[now][k-j][0]+dp[child][j][1]);
					//dp[now][k+2][1]
					dp[now][k+2][1] = _max(dp[now][k+2][1],dp[now][k-j][1]+dp[child][j][0]);
				}
		}
	}

}

int main()
{
	while(scanf("%d%d",&N,&K)!=EOF){
		for(int i=1;i<=N;i++)
			scanf("%d",&val[i]);
		memset(children,0,sizeof(children));
		for(int i=1;i<N;i++){
		    int from,to;
			scanf("%d%d",&from,&to);
			children[from][++children[from][0]] = to;
			children[to][++children[to][0]] = from;
		}

		memset(dp,0,sizeof(dp));

		dfs(1,-1);

		printf("%d\n",dp[1][K][1]);
	}
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值