Poj 1947 Rebuilding Roads (DP_树形DP(背包))

题目链接:http://poj.org/problem?id=1947


题目大意:给定一棵节点数为n的树,问从这棵树最少删除几条边使得某棵子树的节点个数为p,1<=n<=150,1<=p<=n。


解题思路:树形DP + 背包。由于给定的结构是树,就要想到树的递归特性,而树形dp的优美之处是可以利用子树的状态来转移,来求得根的状态。本题要求求最少删除几条边使得子树节点个数为p,我们只要算出每个以节点i为根的树中节点个数为p的最少删除边数,求个最小值就好。其实我们可以这样想,每棵以i为根的树有sum种物品(sum为他以及与他的子孙节点的个数),必须要删除k条边才能使得这棵子树有j个节点(1<=j<=sum),那么每个物品j的费用是k,价值是j,这样问题就转换为在树上的分组背包,总共有n组物品,每次都从以i为根的物品组中选择一个物品进行转移,每组选择一个物品。由于根节点固定了是1,我把树看成有向的树,也就是每次求解都不管父节点,很多人的解题报告里都有管父亲节点,但我觉得那样不好理解。

    现在设dp[i][j]表示以i为根的子树中节点个数为j的最少删除边数(从分组背包角度理解就是到转移到第i组价值为j的最少费用)

    状态转移方程: dp[i][1] = tot                                         (tot为他的子节点个数)

                      dp[i][j] = min(dp[i][j],dp[i][k]-1+dp[s][j-k])  (1<=i<=n,2<=j<=sum(节点总和),1<=k<j,s为i子节点)(i中已有k个节点并从s中选择j-k个,算最少删除边数,s选上所以i->s的边不需删除,所以-1)


测试数据:

2 1
2 1

3 1
1 2
1 3

3 2
1 2
1 3

11 1
1 2
1 3
1 4
1 5
2 6
2 7
2 8
4 9
4 10
4 11

11 6
1 2
1 3
1 4
1 5
2 6
2 7
2 8
4 9
4 10
4 11

11 8
1 2
1 3
1 4
1 5
2 6
2 7
2 8
4 9
4 10
4 11


代码:

#include <stdio.h>
#include <string.h>
#define MAX 500
#define INF 1000000000
#define min(a,b) (a)<(b)?(a):(b)


struct node {

	int v;
	node *next;
}*head[MAX],tree[MAX];
int n,ans,dp[MAX][MAX];
int m,ptr,sum[MAX],vis[MAX];


void AddEdge(int a,int b) {

	tree[ptr].v = b;
	tree[ptr].next = head[a];
	head[a] = &tree[ptr++];


	tree[ptr].v = a;
	tree[ptr].next = head[b];
	head[b] = &tree[ptr++];
}
void Solve_1A(int in) {

	if (vis[in]) return;
	int i,j,k,son,pa,tot;
	
	
	tot = 0;
	vis[in] = sum[in] = 1;
	node *p = head[in];

	
	while (p != NULL) {
	//获取他的子树数量tot,和子节点数量sum[in]
		if (!vis[p->v]) {
		//先出现过的为父节点
			Solve_1A(p->v);
			sum[in] += sum[p->v];
			tot++;
		}
		p = p->next;
	}


	p = head[in];
	//if (in != 1) tot++;  //除了根节点,其他点的都有父节点,要把与父节点相连的边也删去
	dp[in][1] = tot;
	while (p != NULL) {

		int v = p->v; //子节点编号
		for (j = sum[in] + 1; j >= 2; --j)
			for (k = 1; k < j; ++k)
				if (dp[in][k] != INF && dp[v][j-k] != INF)
					dp[in][j] = min(dp[in][j],dp[in][k]-1+dp[v][j-k]);
		p = p->next;
	}
}


int main()
{
	int i,j,k,t;


	while (scanf("%d%d",&n,&m) != EOF) {

		ptr = 1,ans = INF;
		memset(vis,0,sizeof(vis));
		memset(head,NULL,sizeof(head));
		for (i = 0; i <= n; ++i)
			for (j = 0; j <= n; ++j)
				dp[i][j] = INF;


		for (i = 1; i < n; ++i) {

			int a,b;
			scanf("%d%d",&a,&b);
			AddEdge(a,b);
		}


		Solve_1A(1);
		for (i = 1; i <= n; ++i) {

			if (i == 1)
				ans = min(ans,dp[i][m]);
			else ans = min(ans,dp[i][m]+1);//非根节点要删除连到父亲节点的那条边
		}
		printf("%d\n",ans);
	}
}

本文ZeroClock原创,但可以转载,因为我们是兄弟。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 给出一个$n\times m$的矩阵,每个位置上有一个非负整数,代表这个位置的海拔高度。一开始时,有一个人站在其中一个位置上。这个人可以向上、下、左、右四个方向移动,但是只能移动到海拔高度比当前位置低或者相等的位置上。一次移动只能移动一个单位长度。定义一个位置为“山顶”,当且仅当从这个位置开始移动,可以一直走到海拔高度比它低的位置上。请问,这个矩阵中最多有多少个“山顶”? 输入格式 第一行两个整数,分别表示$n$和$m$。 接下来$n$行,每行$m$个整数,表示整个矩阵。 输出格式 输出一个整数,表示最多有多少个“山顶”。 样例输入 4 4 3 2 1 4 2 3 4 3 5 6 7 8 4 5 6 7 样例输出 5 算法1 (递归dp) $O(nm)$ 对于这道题,我们可以使用递归DP来解决,用$f(i,j)$表示以$(i,j)$为起点的路径最大长度,那么最后的答案就是所有$f(i,j)$中的最大值。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要搜一遍,因此需要用双重for循环来枚举每个起点; - 对于已经搜索过的点,需要用一个数组$vis$来记录,防止重复搜索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码 算法2 (动态规划) $O(nm)$ 动态规划的思路与递归DP类似,只不过转移方程和实现方式有所不同。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要搜一遍,因此需要用双重for循环来枚举每个起点; - 对于已经搜索过的点,需要用一个数组$vis$来记录,防止重复搜索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值