POJ1947 - Rebuilding Roads(树形DP)

时间:2022-07-16 18:45:42

题目大意

给定一棵n个结点的树,问最少需要删除多少条边使得某棵子树的结点个数为p

题解

很经典的树形DP~~~直接上方程吧 dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]-1) 方程的意思是 以u结点为根保留j个结点需要删除的最少的边的条数,那么可以选择在某个子结点v中选择k个保留,其他结点保留j-k个,为什么需要-1呢,因为相当于把子树v衔接到结点u上,因此边u->v是不需要删除的,所以要-1

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define MAXN 155
#define INF 0x3f3f3f3f
struct node
{
    int v,next;
};
node edge[MAXN];
int head[MAXN],dp[MAXN][MAXN],p;
void add_edge(int u,int v,int &k)
{
    edge[k].v=v;
    edge[k].next=head[u];
    head[u]=k++;
}
void dfs(int u)
{
    if(head[u]==-1)
    {
        dp[u][1]=0;
        return;
    }
    int cnt=0;
    for(int i=head[u]; i!=-1; i=edge[i].next)
    {
        int v=edge[i].v;
        dfs(v);
        cnt++;
    }
    dp[u][1]=cnt;
    for(int i=head[u]; i!=-1; i=edge[i].next)
    {
        int v=edge[i].v;
        for(int j=p; j>=2; j--)
            for(int k=1; k<=j; k++)
                dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]-1);
    }
}
int main()
{
    int n;
    while(scanf("%d%d",&n,&p)!=EOF)
    {
        int k=1;
        memset(head,-1,sizeof(head));
        memset(dp,INF,sizeof(dp));
        for(int i=1; i<n; i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            add_edge(u,v,k);
        }
        dfs(1);
        int ans=dp[1][p];
        for(int i=2; i<=n; i++)
            ans=min(ans,dp[i][p]+1);
        printf("%d\n",ans);
    }
    return 0;
}