POJ 1947 Rebuilding Roads(树DP)

题意:给一棵树,问最少砍断多少条边才能产生一个恰含m个结点的子树。

分析:将树有根化后,我们考虑以 i 为根结点的子树,要在这棵子树内产生一个结点数目为 j 的子树,要么包含结点 i ,要么不包含,所以定义 dp[i][j][0]表示从以结点 i 为根的子树中得到恰含 j 个节点且不含结点 i 的子树最少需要砍掉的边数,dp[i][j][1]表示从以结点 i 为根的子树中得到恰含 j 个节点且包含结点 i 的子树最少需要砍掉的边数。

初始化:树DP中的初始化其实可以把所有结点都看成是叶子结点来处理,dp[i][0][0]=0,dp[i][1][1]=0,其余的全初始化为INF

状态转移:dp[a][j][0]的转移比较简单,dp[a][j][0]=min(dp[a][j][0],min(dp[b][j][0],dp[b][j][1]+1)),b是a的儿子结点;dp[a][j][1]的转移就是一个类似分组背包的过程,dp[a][j][1]=min(dp[a][j][1],dp[a][j-k][1]+dp[b][k][1]),其中0=<k+1<=j;

View Code
#include <stdio.h>

#include <string.h>

#include <algorithm>

using namespace std;

#define N 151

int n,m,e;

int first[N],next[N],v[N];

int dp[N][N][2];

void init()

{

    e=0;

    memset(first,-1,sizeof(first));

}

void add(int a,int b)

{

    v[e]=b;

    next[e]=first[a];

    first[a]=e++;

}

void dfs(int a)

{

    memset(dp[a],0x3f,sizeof(dp[0]));

    dp[a][0][0]=0;

    dp[a][1][1]=0;



    int i,b;

    for(i=first[a];~i;i=next[i])

    {

        b=v[i];

        dfs(b);

        for(int j=m;j;j--)

        {

            dp[a][j][0]=min(dp[a][j][0],min(dp[b][j][0],dp[b][j][1]+1));

            dp[a][j][1]++;

            for(int k=1;k+1<=j;k++)

            {

                dp[a][j][1]=min(dp[a][j][1],dp[a][j-k][1]+dp[b][k][1]);

            }

        }

    }

}

int main()

{

    int a,b;

    while(~scanf("%d%d",&n,&m))

    {

        init();

        for(int i=1;i<n;i++)

        {

            scanf("%d%d",&a,&b);

            add(a,b);

        }

        dfs(1);

        printf("%d\n",min(dp[1][m][0],dp[1][m][1]));

    }

    return 0;

}

你可能感兴趣的:(Build)