重建道路 树形DP

重建道路 树形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]}
*/

posted @ 2019-09-12 11:25  Santiego  阅读(177)  评论(0编辑  收藏  举报