POJ 1947 树形DP

题意:输入N,P,求一棵N个节点的树最多砍多少次边,能得到一个节点数为P的树。

这个题目还真是一时间考住我了,我怎么都没想通怎么DP,甚至后来没办法去找博客看了一下,还是觉得无法理解。今天上午,翘着水课来刷题,总算是弄懂了。

用dp[rt][j]表示在以rt为根节点的树上,要保留j个节点,(每次j从p递减到1)需要的最小割边数,首先我们来考虑边界,如果是叶子,则dp[rt][1]=0是显而易见的,除此外,它无法构成任何其他数目节点的树,所以dp[rt][i](i为除1外的数)全部置为INF,以此来表示不存在。

dp[rt][j]=min(dp[rt][j]+1,dp[rt][j-k]+dp[nx][k])

即,每次遍历一个子树,如果割掉这个子树,就直接+1(之前我一度以为是要先减去该子树的节点数目后再+1,但其实j就是节点数,你管它能不能形成,不能形成说明本身当前节点数还不够,反正是INF,不影响)。如果不割掉,就把j进行分流,看看分多少个节点给nx最佳

 

发现这类题目,就是01背包的翻版,每次访问子树,都把j从p到1遍历一下,如果根本总体还不够这么多节点,就是INF,反正无影响,如果达到了这个节点量,说明在此子树遍历前,已经有一个暂时的最优解,就进行节点分流,找出当前最优解。。。强调一点,dp[rt][0]必须设置为INF,因为当j=1时,给子树的分流节点没有,但是此时必须割掉子树才能达到dp[rt][1]的效果。。总之,凡是不存在的状态,均用INF表示

 

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
int dp[200][200];

int vis[200];
vector<int> v[200];
int n,p;
void clean()
{
    for (int i=0;i<=n+5;i++){
        v[i].clear();
    }
}
void dfs(int rt)
{
    vis[rt]=1;
    int r=v[rt].size();

    for (int i=0;i<r;i++){
        int nx=v[rt][i];
        if (vis[nx]) continue;
        dfs(nx);

        for (int j=p;j>=1;j--){
            int temp=dp[rt][j]+1;
            for (int k=0;k<j;k++){
                temp=min(temp,dp[rt][j-k]+dp[nx][k]);
            }
            dp[rt][j]=temp;
        }
    }
}
int main()
{
    while (scanf("%d%d",&n,&p)!=EOF)
    {
        clean();
        int i,j;
        for (i=1;i<n;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            v[a].push_back(b);
            v[b].push_back(a);
        }
        memset(vis,0,sizeof vis);
        for (i=0;i<=n+5;i++){
            for (j=0;j<=n+5;j++){
                dp[i][j]=10000000;
            }
            dp[i][1]=0;
        }
        dfs(1);
        int ans=dp[1][p];
        for (i=2;i<=n;i++){
            ans=min(ans,dp[i][p]+1);//如果最优解不是在节点1取到,意味着在最优解的那个点x肯定把1-x给切掉了,故需要+1
        }
        printf("%d\n",ans);
    }
    return 0;
}

 

posted @ 2013-11-04 11:48  KRisen  阅读(329)  评论(0编辑  收藏  举报