题意:给定一棵树,求获取一棵节点数为p的子树需要切割的边的最小数量
做法:定义数组dp[i][j]为以i为根节点获取节点数为j的树需要切割的最小的边的数量。对于i的一个子节点v,如果不要以v为根节点的整棵子树,那么dp[i][j]=dp[i][j]+1(因为要切割i与v的相连的边)。否则,可以枚举以v为根节点的子树的大小,假设为W,那么dp[i][j]=min(dp[i][j].dp[i][j-w]+dp[v][w])。
一些小细节:如果是开二维数组来做,像上面一样的,那么对于当前的根节点i,在枚举第二维的时候需要逆序枚举(就像01背包的做法)。还有,dp[i][1]一开始要初始化为0,dp[i][sum[i]]=0(sum[i]是i节点为根节点的整棵子树的大小。
#define _CRT_SECURE_NO_WARNINGS
#include<algorithm>
#include<iostream>
#include<ctime>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<climits>
using namespace std;
const int maxn = 155;
const int inf = INT_MAX - 5;
int fir[maxn], vv[maxn], nxt[maxn], e;
int sonnum[maxn], dp[maxn][maxn], desc[maxn];
void add(int a, int b)
{
vv[e] = b;
nxt[e] = fir[a];
fir[a] = e++;
}
void dfs(int cur)
{
desc[cur] = 1;
for (int i = fir[cur]; i != -1; i = nxt[i])
{
int v = vv[i];
dfs(v);
desc[cur] += desc[v];
}
}
void dfs1(int cur)
{
dp[cur][1] = 0;
dp[cur][desc[cur]] = 0;
int sonind = 0;
for (int i = fir[cur]; i != -1; i = nxt[i])
{
sonind++;
int v = vv[i];
dfs1(v);
for (int j = desc[cur]; j >=1; j--)
{
if (dp[cur][j] != inf) dp[cur][j] = dp[cur][j] + 1;//cut the edge to this son
for (int k = 1; k <= desc[v]; k++)
{
if (k >= j) break;
if (dp[cur][j - k]!=inf && dp[v][k]!=inf)
dp[cur][j] = min(dp[cur][j], dp[cur][j - k] + dp[v][k]);
}
}
}
}
int main()
{
int n, p;
while (scanf("%d%d", &n, &p) != EOF)
{
e = 0;
for (int i = 1; i <= n; i++)
{
fir[i] = -1;
}
for (int i = 1; i <= n; i++)
{
for (int k = 0; k <= n; k++)
{
dp[i][k] = inf;
}
}
memset(sonnum, 0, sizeof(sonnum));
memset(desc, 0, sizeof(desc));
int a, b;
for (int i = 1; i <= n - 1; i++)
{
scanf("%d%d", &a, &b);
add(a, b);
sonnum[a]++;
}
dfs(1);
dfs1(1);
int ans = INT_MAX;
ans = min(dp[1][p],ans);
for (int i = 2; i <= n; i++)
{
ans = min(ans, dp[i][p] + 1);
}
printf("%d\n", ans);
}
}