luogu_P1272 重建道路

很好的一道树上DP

我会告诉你我想那个“-2”想了一个下午吗?


题面:
一场可怕的地震后,人们用N个牲口棚(1≤N≤150,编号1..N)重建了农夫John的牧场。由于人们没有时间建设多余的道路,所以现在从一个牲口棚到另一个牲口棚的道路是惟一的。因此,牧场运输系统可以被构建成一棵树。John想要知道另一次地震会造成多严重的破坏。有些道路一旦被毁坏,就会使一棵含有P(1≤P≤N)个牲口棚的子树和剩余的牲口棚分离,John想知道这些道路的最小数目。

输入:
第1行:2个整数,N和P
第2..N行:每行2个整数I和J,表示节点I是节点J的父节点。

输出:
单独一行,包含一旦被破坏将分离出恰含P个节点的子树的道路的最小数目。


分析:

\(dp[i][j]\)表示计算至\(i\)时,分离出整颗大小为\(j\)的子树(包括\(i\))所需切掉的最少道路数。

初始化一个点的时候,把它的所有连边给去掉,\(dp[i][1]=du[i]\)

父节点每与一个子节点合并,需要将dp之和-2,为什么?

因为在初始化时,已经将u的\(u->v\)去掉,也已经将\(v\)\(v->u\)去掉,而如果想将含\(u\)连通块与含\(v\)连通块相连,这条边不能被去掉。

然后我一直在想为什么每一次都要减去2

答案是:我们的dp操作规则是默认一开始所有的点都被切开,所以我们的运算要遵循这个规则,每一次计算也就是一次合并,无论哪两个dp值都遵循着不与外界联系的规律,所以两个dp值相加一定要-2.


code:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=201;
struct Edge{
    int to,next;
}e[N*2];
int du[N],a[N],dp[N][N];
int n,k,res=INF,EdgeCnt=0;
void addedge(int u,int v){
    int p=++EdgeCnt;
    e[p].to=v;e[p].next=a[u];
    a[u]=p;
}
void dfs(int u,int fa){
    dp[u][1]=du[u];
    for (int p=a[u];p;p=e[p].next){
        int v=e[p].to;
        if (v!=fa){
            dfs(v,u);
            for (int j=k;j>=1;j--)
                for (int k=1;k<=j;k++)
                    dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]-2);
        }
    }
    res=min(res,dp[u][k]);
}
int main(){
    scanf("%d%d",&n,&k);
    memset(dp,0x3f,sizeof(dp));
    for (int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        addedge(u,v);
        addedge(v,u);
        du[u]++;du[v]++;
    }
    dfs(1,0);
    printf("%d",res);
    return 0;
}
posted @ 2020-11-24 17:50  GUO_dx  阅读(56)  评论(0编辑  收藏  举报