分析:
明显的树形背包
令
f
u
,
i
f_{u,i}
fu,i表示节点
u
u
u的子树保留
i
i
i个节点所需要删去的最小边数(计算
u
u
u与父节点的连边)。转移时对每一个点跑一边背包即可。我们依次枚举
u
u
u的所有儿子,令当前枚举到的儿子为
v
v
v,有方程(注意不取
v
v
v时为原来的
f
u
,
i
f_{u,i}
fu,i,需单独拿出来):
f
u
,
i
=
m
i
n
(
f
u
,
i
,
min
j
=
1
i
−
1
{
f
u
,
i
−
j
+
f
v
,
j
−
2
}
)
(
i
=
1
,
2
,
⋯
,
p
)
f_{u,i} = min(f_{u,i},\min_{j = 1}^{i - 1}\{f_{u,i - j} + f_{v,j} - 2\})\;(i = 1,2,\cdots,p)
fu,i=min(fu,i,j=1mini−1{fu,i−j+fv,j−2})(i=1,2,⋯,p)
初始化显然: f u , 1 = d u ( u = 1 , 2 , ⋯ , n ) f_{u,1} = d_u\;(u = 1,2,\cdots,n) fu,1=du(u=1,2,⋯,n),其中 d u d_u du表示点 u u u的度数。
为什么要减二?
因为 f v , j f_{v,j} fv,j中计算了点 v v v与父亲(即点 u u u)的连边, f u , i f_{u,i} fu,i中也计算了点 u u u与点 v v v的连边(原本不包含点 v v v),而若取了点 v v v, u u u与 v v v的连边是不用被删去的,所以这条边被多计算了两边(这也是为什么不取 v v v时要单独拿出来)。
要注意倒序枚举 i i i,最终答案为 max u = 1 n { f u , p } \max\limits_{u = 1}^n\{f_{u,p}\} u=1maxn{fu,p}
Code:
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 200,inf = 1e9;
int n,p,x,y,cnt,ans = inf,d[maxn],last[maxn],f[maxn][maxn];
struct edge{
int v,nxt;
}e[2 * maxn];
int read(){
int x = 0;
char c = getchar();
while(c < '0' || c > '9') c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + (c ^ 48),c = getchar();
return x;
}
inline void insert(int x,int y){
cnt ++,e[cnt].v = y,e[cnt].nxt = last[x],last[x] = cnt;
}
void dfs(int u,int fa){
f[u][1] = d[u];
for(int i = last[u]; i; i = e[i].nxt){
int v = e[i].v;
if(v == fa) return;
dfs(v,u);
for(int j = p; j >= 1; j --)//倒序枚举
for(int k = 1; k < j; k ++)
f[u][j] = min(f[u][j],f[v][k] + f[u][j - k] - 2);
}
}
int main(){
n = read(),p = read();
for(int i = 1; i <= n; i ++)
for(int j = 0; j <= p; j ++)
f[i][j] = inf;
for(int i = 1; i < n; i ++){
x = read(),y = read(),d[x] ++,d[y] ++;
insert(x,y),insert(y,x);
}
dfs(1,0);//任选一点为根即可
for(int i = 1; i <= n; i ++) ans = min(ans,f[i][p]);
cout << ans << endl;
return 0;
}