poj 2486 Apple Tree

题目:http://poj.org/problem?id=2486

【题意】:给一棵树,每个节点上有一些苹果,根节点编号为1,从根节点开始走,一共走K步(一步走一条边),问最多能吃到多少苹果

【思路】:dp~~  这个题的状态设置有多种方法,此题有一个特性,即:对于每一种走法,所走子树的顺序不影响最终的结果

①dp[i][j][0/1],以i为根节点,最多可以走j步最后回到(即1)i点,最多吃多少苹果,不一定回到(即0),最多吃多少苹果

dp[root][j][1]=max(dp[root][j][1],dp[go][t][1]+dp[root][j-2-t][1]);  go表示root的儿子
dp[root][j][0]=max(dp[root][j][0],dp[go][t][1]+dp[root][j-t-2][0]);  先从这棵子树上回来,再走其他的子树
dp[root][j][0]=max(dp[root][j][0],dp[go][t][0]+dp[root][j-t-1][1]);  先从其他的子树上回来,再去这棵子树上,不回到i

这也是我一开始的想法,不过WA了10次。。

原因有二:算dp[root][j][0]之前不能将将dp[root][j][1]算出来=_=(请观察转移方程,巨坑),还有就是初始化(后文再谈)

②dp[i][j][0/1],以i为根节点,最多可以走j步最后回到(即1)i点,最多吃多少苹果;一定不回到(即0),最多吃多少苹果

以某个节点为根走K步,结果最大的一定是dp[i][K][0],这是因为最后一步回到i,还不如在子树上闲逛。。

dp[root][j][1]=max(dp[root][j][1],dp[go][t][1]+dp[root][j-2-t][1]);
dp[root][j][0]=max(dp[root][j][0],dp[go][t][1]+dp[root][j-t-2][0](上文已说明为何不是1));先去这棵子树,返回,再去其他子树
dp[root][j][0]=max(dp[root][j][0],dp[go][t][0](上文已说明为何不是1)+dp[root][j-t-1][1]);先去其他子树返回,再去这棵子树


两种方式的转移方程是一样的。。。但②中运用了贪心,,不然方程要复杂一些

现在假设  root 为根的所有儿子的状态都已求出,即dp[所有的go][0~K][0/1]都求出来了,怎样算出root的所有状态呢?

考虑每一棵子树,每棵子树假定只能走一次(走过了,再走是没有用的),只有走或不走的情况, 对应选或不选的情况

01背包了,不过这里比较特殊,在选的状态里  不返回  ,会使后面的子树都不能选择(这也是状态设置为两个的原因),但不必理会,转移方程已体现了这一点。

仔细体会01背包


最后谈一下初始化(简直无语巨坑),按照①的想法来,对于每一个节点dp[i][0-K][0/1]=num[i],最多走K步,若把i的子树遍历完了,k还有多的,反正是最多,后面不走便是。

【代码】

按照①的想法来写

<span style="font-size:18px;">#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
struct Edge
{
    int go,next;
}edge[2*110];
int head[110],tot;
void addedge(int a,int b)
{
    edge[tot].go=b;
    edge[tot].next=head[a];
    head[a]=tot++;
}

int N,K;
int dp[110][210][2];
int num[110];

void dfs(int root,int fa)
{
    for(int i=head[root];i!=-1;i=edge[i].next)
    {
        int go=edge[i].go;
        if(go==fa)continue;
        dfs(go,root);

        for(int j=K;j>=0;j--)
        {
            //cout<<"root = "<<root<<ends<<dp[root][j][0]<<endl;
            for(int t=0;t<=j-1;t++)
            {
                if(t<j-1)dp[root][j][1]=max(dp[root][j][1],dp[go][t][1]+dp[root][j-2-t][1]);
                if(t<j-1)dp[root][j][0]=max(dp[root][j][0],dp[go][t][1]+dp[root][j-t-2][0]);
                dp[root][j][0]=max(dp[root][j][0],dp[go][t][0]+dp[root][j-t-1][1]);
            }
        }
    }
}

int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    while(scanf("%d%d",&N,&K)!=EOF)
    {

        memset(head,-1,sizeof(head));
        tot=0;
        
        for(int i=1;i<=N;i++)
        {
            scanf("%d",&num[i]);
            for(int j=0;j<=K;j++)
                dp[i][j][0]=dp[i][j][1]=num[i];
        }
        for(int i=1;i<N;i++)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            addedge(a,b);
            addedge(b,a);
        }

        dfs(1,-1);
//        for(int i=1;i<=N;i++)
//        {
//            cout<<"i = "<<i<<endl;
//            for(int j=0;j<=K;j++)
//                cout<<dp[i][j][0]<<ends;
//            cout<<endl;
//        }
        int res=dp[1][K][0];
        cout<<res<<endl;
    }
    return 0;
}
</span>








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值