不太容易得到状态方程及其转移, 花了好长时间理解。
dp[ i ][ j ] :从以 i 为根的子树中分离出 j 个联通的节点至少需要砍掉的边数(根节点必须保留,暂未考虑将 i 和它的父亲分离)。
下面说一下思考的过程:
初始化: dp [ i ][ j ] = INF ( j >=2 && j<=P) , dp[ i ][ 1 ] = i 的儿子个数。即把 i 的所有儿子都砍掉了。
再依次考虑每个 i 的儿子是否需要连接:
若不连, dp[ i ][ j ]不变 , 即dp[ i ][ j ] = dp[ i ][ j ] ;
若连上该儿子: dp[ i ][ j ] = min { dp[ i ][ j-k] + dp[ son[i] ][ k ] -1 ; // 原来砍掉了所有儿子, 现在从新连上来,故需要删除的边数 -1
故得转移: “ dp[root][j] = min(dp[root][j],dp[root][j-k]+dp[u][k]-1) ”
代码:
#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=155;
int fa[maxn],dp[maxn][maxn]; //dp[i][j] : 从以i为根的子树中得到j个点至少删除的边数
vector<int>tree[maxn];
int N,P;
void DFS(int root) //注意:dp[root][j]表示从以root为根的树得到j个点
//最少需要砍掉的边数, 还未考虑将树root从整棵树分离
{
int i,j,k,u,m=tree[root].size();
for(int i=0;i<=P;i++) dp[root][i]=INF; //初始状态:把所有子树都砍掉了
dp[root][1]=m; //只有根节点本身
for(i=0;i<m;i++){ //再依次考虑每棵子树:是否需要连接
u=tree[root][i];
DFS(u);
for(j=P;j>=1;j--){ // 因为计算dp[root][j]依赖dp[root][j-k]
//(还没有连接u子树时的状态),避免覆盖,故逆序
for(k=1;k<j;k++){
dp[root][j] = min(dp[root][j],dp[root][j-k]+dp[u][k]-1);
//连接u子树要少砍掉一条边,故减一
}
}
}
}
int main()
{
while(scanf("%d%d",&N,&P)!=EOF)
{
memset(fa,0,sizeof(fa));
for(int i=0;i<=N;i++)
tree[i].clear();
int s,f;
for(int i=1;i<N;i++){
scanf("%d%d",&f,&s);
tree[f].push_back(s),fa[s]=f;
}
int root=1;
while(fa[root]) root=fa[root];
DFS(root);
int ans=INF;
for(int i=1;i<=N;i++){
ans=min(ans,dp[i][P]+(fa[i]?1:0));
}
printf("%d\n",ans);
}
return 0;
}