题目链接:
http://poj.org/problem?id=1947
题解:
开一个dp数组,dp[i][j]表示以第i个节点为根,保留j个点所需要的刀数。可以先将所有dp[i][1]初始化为 i的儿子数(仅保留这个点需要的刀数),其他位置初始化为无限大。然后将问题看做一个01背包,物品的容量就是m,此时和普通的01背包区别为背包里的物品(也就是子树)可以选择k个节点砍掉或者不砍(j + k <= m)。所以d[i][j] = d[i][j-k]+d[son[i]][k]-1;(两子树拆开需要一刀)。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
int dp[200][200], father[200];
vector <int> fson[200];
int n,m;
void init()
{
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
dp[i][j] = 1000000;
}
}
int dfs(int root)
{
for(int i = 0; i < fson[root].size(); i++)
dfs(fson[root][i]);
dp[root][1] = fson[root].size();//只要一个点时有多少个儿子砍多少刀
for(int i = 0; i < fson[root].size(); i++)
for(int j = m - 1; j > 0; j--)//这里j需要从大往小和01背包类似, m为背包容量,dp记载花费,k代表我要拿的物品子树内的点,j代表我要拿的物品子树外的点
if(dp[root][j] != 1000000)
for(int k = 1; k <= m - j; k++)
if(dp[fson[root][i]][k] != 1000000)
dp[root][j + k] = min(dp[root][j] + dp[fson[root][i]][k]-1,dp[root][j + k]);//我砍了物品子树内的点和物品子树外的点所需要的最小花费
return dp[root][m];
}
int main()
{
while(cin >> n >> m)
{
init();
int n1,n2,cnt = 0;
for(int i= 0; i < n - 1; i++)
{
cin >> n1 >> n2;
father[n2] = n1;
fson[n1].push_back(n2);
}
int root;
for(int i = 1; i <= n; i++)
if(!father[i])
root = i;
int minn = dfs(root);
for(int i = 1; i <= n; i++)
{
if(i != root && minn > dp[i][m])
minn = dp[i][m] + 1; //之所以加1是需要将以i为根的子树与整棵树分离
}
cout << minn <<endl;
memset(father,0,sizeof(father));
memset(dp,0,sizeof(dp));
for(int i = 0;i <= n; i++)
fson[i].clear();
}
return 0;
}