重建道路 树形DP
给一棵树,问最少断多少边使得这棵树树最终只有\(p\)个节点
设计dp状态\(f[u][i][j]\)表示节点\(u\),到第\(i\)个儿子,使\(j\)个节点分离,但是不分离\(u\)最少需要断的边数。类比背包,容易得到转移方程:
\[f[u][i][j]=min{f[u][i-1][j-k]+f[v][n][k]}
\]
\]
再优化一维\(i\),状态变为\(f[u][j]\),此时必须倒序遍历\(j\)。
需要注意的是,最后答案并不是\(f[1][sz[1]-p]\),因为最后可能把节点1也删了,所以必须在每个满足子树节点数\(\ge p\)的节点处统计一下答案。
\[ans=min(f[u][sz[u]-p]+f[u][sz[u]], ans)
\]
\]
其中注意\(f[1][sz[1]]=0\),因为不需要将树根与其父亲分离(它没父亲)
#include <cstdio>
#include <cstring>
#define MAXN 155
#define MIN(A,B) ((A)<(B)?(A):(B))
using namespace std;
int n,p,ans;
int head[MAXN],nxt[MAXN*2],vv[MAXN*2],tot;
inline void add_edge(int u, int v){
vv[++tot]=v;
nxt[tot]=head[u];
head[u]=tot;
}
int f[MAXN][MAXN],sz[MAXN];
void load(int u, int fa){
sz[u]=1;
f[u][0]=0;
for(int i=head[u];i;i=nxt[i]){
int v=vv[i];
if(v==fa) continue;
load(v, u);
sz[u]+=sz[v];
}
f[u][sz[u]]=1;
}
void dfs(int u, int fa){
for(int i=head[u];i;i=nxt[i]){
int v=vv[i];
if(v==fa) continue;
dfs(v, u);
for(int j=sz[u];j>=0;--j)
for(int k=0;k<=MIN(sz[v], j);++k){
f[u][j]=MIN(f[v][k]+f[u][j-k], f[u][j]);
}
}
if(sz[u]>=p) ans=MIN(f[u][sz[u]-p]+f[u][sz[u]], ans);
}
int main(){
scanf("%d %d", &n, &p);
for(int i=2;i<=n;++i){
int u,v;
scanf("%d %d", &u, &v);
add_edge(u, v);
add_edge(v, u);
}
memset(f, 0x3f, sizeof(f));
ans=0x3f3f3f3f;
load(1, 0);
f[1][sz[1]]=0;
dfs(1, 0);
printf("%d", ans);
return 0;
}
/*
f[u][i][j]=min{f[u][i-1][j-k]+f[v][n][k]}
*/