题目链接:点击这里
题目大意:
给你一棵
n
n
n 个节点的树,求最少删掉几条边才能得到一颗
p
p
p 节点的子树
题目分析:
很明显这是一道树形dp。其状态也很容易发现:
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 为以
i
i
i 为根的子树分割出一颗
p
p
p 节点的子树需要删的边的数目。
其初始条件也很容易确定:由于
d
p
[
i
]
[
1
]
dp[i][1]
dp[i][1] 表示只剩一个节点的子树即只剩了
i
i
i 点本身,所以
d
p
[
i
]
[
1
]
dp[i][1]
dp[i][1] 其儿子节点的数目
此
d
p
dp
dp 的转移方程为:
d
p
[
i
]
[
j
]
=
m
i
n
(
d
p
[
i
]
[
j
]
,
d
p
[
i
]
[
j
−
k
]
+
d
p
[
s
o
n
]
[
k
]
−
1
)
dp[i][j]=min(dp[i][j],dp[i][j-k]+dp[son][k]-1)
dp[i][j]=min(dp[i][j],dp[i][j−k]+dp[son][k]−1)
我们感性解释一下这个方程,我们需要找到一颗以
i
i
i 节点为根的大小为
p
p
p 的子树,这颗子树可以通过保留
i
i
i 节点的
j
−
k
j-k
j−k 个孩子,再从枚举这些孩子节点从他们中在分割出
k
k
k 个节点与之相连即可,上述方程的-1是因为
i
i
i 节点和他的儿子节点之间的边被计算了两次
具体细节见代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
int read()
{
int res = 0,flag = 1;
char ch = getchar();
while(ch<'0' || ch>'9')
{
if(ch == '-') flag = -1;
ch = getchar();
}
while(ch>='0' && ch<='9')
{
res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
ch = getchar();
}
return res*flag;
}
const int maxn = 155;
const int mod = 1e9+7;
const double pi = acos(-1);
const double eps = 1e-8;
int n,p,in[maxn],dp[maxn][maxn];
vector<int> v[maxn];
void dfs(int u)
{
for(int i = 0;i < v[u].size();i++)
{
dfs(v[u][i]);
for(int j = p;j >= 1;j--)
for(int k = 1;k < j;k++)
dp[u][j] = min(dp[u][j],dp[u][j-k]+dp[v[u][i]][k]-1);
}
}
int main()
{
n = read(),p = read();
for(int i = 1;i < n;i++)
{
int a = read(),b = read();
v[a].push_back(b);
in[b]++;
}
memset(dp,0x3f,sizeof(dp));
int root;
for(int i = 1;i <= n;i++)
if(!in[i])
{
root = i;
break;
}
for(int i = 1;i <= n;i++)
dp[i][1] = v[i].size();
dfs(root);
int ans = dp[root][p];
for(int i = 1;i <= n;i++)
if(i != root)ans = min(ans,dp[i][p]+1);//+1是因为要剪断与父亲节点的联系
printf("%d\n",ans);
return 0;
}