根据树上背包模型可以想到状态定义:
d
p
[
u
]
[
i
]
dp[u][i]
dp[u][i]表示以u结点为根结点的子树生成只有i个结点的最少去掉的边数,转移方程如下:
d
p
[
u
]
[
i
+
j
]
=
m
i
n
{
d
p
[
u
]
[
i
]
+
d
p
[
s
o
n
]
[
j
]
}
dp[u][i + j] = min\{ dp[u][i] + dp[son][j]\}
dp[u][i+j]=min{dp[u][i]+dp[son][j]}
这里有一个技巧,为了防止转移时覆盖,固用一个tmp函数存起计算的状态
其余需要注意的细节看代码注释
ac代码如下
#include<cstdio>#include<cstring>#include<algorithm>#include<vector>#include<cctype>inlinelonglongIO(){longlong x =0;bool f =false;char c =getchar();while(!isdigit(c)){if(c =='-') f =true;
c =getchar();}while(isdigit(c)){
x =(x <<1)+(x <<3)+(c -'0');
c =getchar();}return f ?-x : x;}#define ll long longusingnamespace std;constint M =2e3+5;constint maxn =2e3+5, maxm =2e3+5;constint INF =0x3f3f3f3f;int head[maxn], cnt;//初始化voidinit(){memset(head,-1,sizeof head); cnt =-1;}struct edges {int to, next;}edge[maxm <<1];//无向图则需要乘2inlinevoidadd(int u,int v){
edge[++cnt]={.to = v,.next = head[u]};
head[u]= cnt;}int dp[M][M], siz[M], tmp[M];int n, m;voiddfs(int u,int fa){// 初始化,根据树形背包模型,一开始的只保留一个结点去掉的边数为0, 保留0个的去掉的边数是1,即拼接上去的那条边
siz[u]=1, dp[u][1]=0, dp[u][0]=1;for(int i = head[u];~i; i = edge[i].next){int v = edge[i].to;if(v == fa)continue;dfs(v, u);for(int j =1; j <= siz[u]+ siz[v];++j) tmp[j]= INF;// 用一个tmp存计算的状态,防止转移覆盖原来需要用到的dp值for(int j =1; j <= siz[u];++j){// 当前树保证必须选一个for(int k =0; k <= siz[v];++k){// 子树从0开始
tmp[j + k]=min(tmp[j + k], dp[u][j]+ dp[v][k]);}}
siz[u]+= siz[v];for(int j =1; j <= siz[u];++j) dp[u][j]= tmp[j];}}intmain(){
n =IO(), m =IO();init();for(int i =1; i < n;++i){int u =IO(), v =IO();add(u, v),add(v, u);}memset(dp,0x3f,sizeof dp);// 所有都初始化为最大值dfs(1,-1);int ans = dp[1][m];for(int i =2; i <= n;++i)if(siz[i]>= m) ans =min(ans, dp[i][m]+1);// 最终的答案不一定是在1号结点上去边, +1是因为要加上去除连接父亲结点的边printf("%d", ans);return0;}