题目链接
dp[root][j]:以root为根节点的子树,得到 j 个节点的子树需要最少减掉的边数,注意子树中必须保留root节点。否则无法dp
那么很明显的边界条件dp[root][1] = num(儿子的个数),因为要只剩一个节点的子树,那么所有的孩子都减掉,这样就为儿子的个数。
那么状态转移方程呢
dp[root][i] = min(dp[root][i-k]+dp[child][k] - 1,dp[root][i]);
其实就是要得到一个i个节点的子树,枚举所有的孩子为k个节点的,当前root保留 i-k 个节点,然后把root和child之间之前被剪断的连接起来,所以这里要减1
注意非根节点的答案要加一
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
const int N=200;
using namespace std;
int num[N];//num[i]表示i节点的子节点个数
int sum[N];//sum[i]以i为根的树的所有节点个数
int dp[200][200];//dp[i][j]以i为根的树还剩j个节点所删去边的最小值
vector<int>v[200];//v[x][y]x与y相连
void dfs(int root)
{
sum[root]=1;//漏了,根节点本身算一个
if(v[root].size()==0)//叶子节点
{
sum[root]=1;
dp[root][1]=0;
return;
}
for(int i=0;i<v[root].size();i++)//枚举root的子节点
{
int child=v[root][i];//子节点
dfs(child);
sum[root]+=sum[child];
for(int j=sum[root];j>0;j--)
for(int k=1;k<j;k++)
dp[root][j]=min(dp[root][j],dp[root][j-k]+dp[child][k]-1);
}
}
int main()
{
//freopen("in.txt","r",stdin);
memset(num,0,sizeof(num));
memset(sum,0,sizeof(sum));
memset(dp,0x3f,sizeof(dp));
int n,p,i,x,y;
scanf("%d%d",&n,&p);//又漏了这句
for(i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
v[x].push_back(y);
num[x]++;
}
for(i=1;i<=n;i++)
dp[i][1]=num[i];//将子节点全部删完
dfs(1);
int ans=dp[1][p];
for(i=2;i<=n;i++)//扫描找最小值
ans=min(ans,dp[i][p]+1);//漏了+1,把根节点和父节点断开
printf("%d\n",ans);
return 0;
}