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原创,但可以转载,因为我们是兄弟。

你可能感兴趣的:(struct,tree,测试,null)