poj 1947

题目连接:http://poj.org/problem?id=1947

题意:

给你一颗树,问去掉一个n个节点的子树所需要删除的最少边数。

思路:可以简化处理:先求出对于每个点形成一个n个节点的树最少需要删除多少边,然后对于原树中的根,因为是它本身,所以不用+1,其他的因为还要切断与根的关系,要+1处理;

对于求以每个节点为根的树形成只有n个节点所需删除最少的边数的问题。用树状dp来做;

dp【i】【j】表示以i为根,拥有j个节点所需要删除的最少边。

方法一:

首先,认为每个节点只有其本身。那么初始化dp【i】【1】=0;

那么动态转移:

删除某个子节点的话:temp=dp【i】【j】+1;

不删除的话:dp【i】【j-k】+dp【子节点】【k】

所以:dp【i】【j】=min(temp,dp【i】【j-k】+dp【子节点】【k】);

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 0xfffffff
using namespace std;
int next[240][240];
int mark[240];
int bb;
int dp[240][240];
void  dfs(int aa)
{
    for(int ii=1;ii<=next[aa][0];ii++)
    dfs(next[aa][ii]);
    dp[aa][1]=0;
    for(int ii=1;ii<=next[aa][0];ii++)
    {
        for(int kk=bb;kk>=1;kk--)
        {
            int temp=dp[aa][kk]+1;
            for(int jj=1;jj<=kk;jj++)
            {
            temp=min(temp,dp[next[aa][ii]][jj]+dp[aa][kk-jj]);
            //cout<<"aa = "<<aa<<' '<<"son = "<<next[aa][ii]<<' '<<"many = "<<jj+kk<<' '<<dp[aa][jj+kk]<<endl;
            }
            dp[aa][kk]=temp;
        }
    }
    return ;
}
int DP(int aa,int nn)
{
    dfs(aa);
    int getmin=N;
    for(int ii=1;ii<=nn;ii++)
    {
        if(ii==aa)continue;
        getmin=min(getmin,dp[ii][bb]+1);
    }
    getmin=min(getmin,dp[aa][bb]);
    return getmin;
}
void init(int nn)
{
    for(int ii=1;ii<=nn;ii++)
    {
        for(int jj=0;jj<=nn;jj++)
        dp[ii][jj]=N;
        next[ii][0]=0;
    }
    return ;
}
int main()
{
    int i,j,m,n;
    int a,b;
    while(scanf("%d%d",&m,&n)!=EOF)
    {
        bb=n;
        memset(mark,0,sizeof(mark));
        init(m);
        for(i=1;i<m;i++)
        {
            scanf("%d%d",&a,&b);
            next[a][++next[a][0]]=b;
            mark[b]=1;
        }
        int temp;
        for(i=1;i<=m;i++)
        {
            if(!mark[i])
            {
                temp=DP(i,m);
                break;
            }
        }
        printf("%d\n",temp);
    }
    return 0;
}


方法二:

首先,默认每个节点有其子节点,但是,都被删除掉了。那么初始化dp【i】【1】=(子节点的个数);

动态转移方程:

dp【i】【jj+kk】=min(dp【i】【jj】+dp【子节点】【kk】-1,dp【i】【jj+kk】);

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 0xfffffff
using namespace std;
int next[240][240];
int mark[240];
int bb;
int dp[240][240];
void  dfs(int aa)
{
    for(int ii=1;ii<=next[aa][0];ii++)
    dfs(next[aa][ii]);
    dp[aa][1]=next[aa][0];
    for(int ii=1;ii<=next[aa][0];ii++)
    {
        for(int kk=bb-1;kk>=1;kk--)
        {
            if(dp[aa][kk]<N)
            for(int jj=1;jj<=bb-1;jj++)
            if(dp[next[aa][ii]][jj]<N)
            {
            dp[aa][jj+kk]=min(dp[aa][jj+kk],dp[next[aa][ii]][jj]+dp[aa][kk]-1);
            //cout<<"aa = "<<aa<<' '<<"son = "<<next[aa][ii]<<' '<<"many = "<<jj+kk<<' '<<dp[aa][jj+kk]<<endl;
            }
        }
    }
    return ;
}
int DP(int aa,int nn)
{
    dfs(aa);
    int getmin=N;
    for(int ii=1;ii<=nn;ii++)
    {
        if(ii==aa)continue;
        getmin=min(getmin,dp[ii][bb]+1);
    }
    getmin=min(getmin,dp[aa][bb]);
    return getmin;
}
void init(int nn)
{
    for(int ii=1;ii<=nn;ii++)
    {
        for(int jj=0;jj<=nn;jj++)
        dp[ii][jj]=N;
        next[ii][0]=0;
    }
    return ;
}
int main()
{
    int i,j,m,n;
    int a,b;
    while(scanf("%d%d",&m,&n)!=EOF)
    {
        bb=n;
        memset(mark,0,sizeof(mark));
        init(m);
        for(i=1;i<m;i++)
        {
            scanf("%d%d",&a,&b);
            next[a][++next[a][0]]=b;
            mark[b]=1;
        }
        int temp;
        for(i=1;i<=m;i++)
        {
            if(!mark[i])
            {
                temp=DP(i,m);
                break;
            }
        }//题目没有说一定是1为所给树的祖先吧
        printf("%d\n",temp);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值