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