ZOJ 2834 Maximize Game Time 树形背包,多trik题

题意:打游戏。有一个恶魔家族,你的任务是杀光他们,并救出公主,一旦任务完成,游戏结束,问一次游戏最多能玩多久。家族的结构是树状的,如果一个父节点的两个或两个以上的儿子被杀,那么就必须先杀了父节点,否则公主会不测。trick1:这里说的一个家族,最终的结构可能是森林状态。。。

做法:一个家族里有小家族,一棵树里有子树。分成三种状态:杀光一个小家族(kal),杀了一个家族的最长者和部分子孙(skn),不杀长者杀子孙(nkn)。(用贪心的策略领悟即可)。其实真正的挑战在编写阶段,trick巨多。

#include<stdio.h>
#include<string.h>
#define LMT 1005
typedef struct
{
    int u,v,next;
}line;
line e[LMT<<1];
int kal[LMT],skn[LMT],nkn[LMT],wei[LMT],next[LMT],snkn[LMT],fa[LMT],dp[LMT];
int n,all;
inline int max(int a,int b)
{
    return a>b?a:b;
}
void insert(int u,int v)
{
    e[all].u=u;
    e[all].v=v;
    e[all].next=next[u];
    next[u]=all++;
    e[all].v=u;
    e[all].u=v;
    e[all].next=next[v];
    next[v]=all++;
}
void inidfs(int u)
{
    int v,x,max=-1;
    kal[u]=wei[u];
    for(x=next[u];x!=-1;x=e[x].next)
    if(e[x].v!=fa[u])
    {
        v=e[x].v;
        inidfs(v);
        kal[u]+=kal[v];
    }
}
void ndfs(int u)
{
    nkn[u]=0;
    int v,x;
    if(snkn[u]==-1)//trick2:一个最优的nkn方案不一定是杀光一个最大子家族,加上另外子家族的nkn
    {
        snkn[u]=0;
        for(x=next[u];x!=-1;x=e[x].next)//居然会在这里多加个分号,一个分号的血案。。。
        if(e[x].v!=fa[u])
        {
            v=e[x].v;
            if(nkn[v]==-1)ndfs(v);
            snkn[u]+=nkn[v];
        }
    }
    for(x=next[u];x!=-1;x=e[x].next)
    if(e[x].v!=fa[u])
    {
        v=e[x].v;
        nkn[u]=max(nkn[u],snkn[u]-nkn[v]+kal[v]);
    }
}
void dfs(int u)
{
    skn[u]=wei[u];
    int v,vv,x,xx;
    if(snkn[u]==-1)
    {
        snkn[u]=0;
        for(x=next[u];x!=-1;x=e[x].next)
        if(e[x].v!=fa[u])
        {
           if(nkn[e[x].v]==-1)ndfs(e[x].v);
           snkn[u]+=nkn[e[x].v];
        }
    }
    if(dp[u]==-1)//要懂得使用不一定这个副词,很多东西都是不一定的,因此有了DP
    {
        dp[u]=0;
        for(x=next[u];x!=-1;x=e[x].next)
        if(e[x].v!=fa[u])
        {
            for(v=e[x].v,xx=next[u];xx!=-1;xx=e[xx].next)
            if(e[xx].v!=v&&e[xx].v!=fa[u])
            {
                vv=e[xx].v;
                if(skn[vv]==-1)dfs(vv);
                dp[u]=max(dp[u],snkn[u]-nkn[v]-nkn[vv]+kal[v]+skn[vv]);
            }
            dp[u]=max(dp[u],snkn[u]-nkn[v]+kal[v]);
        }
    }
    skn[u]+=dp[u];
}
int main()
{
    int i,ans;
    while(scanf("%d",&n)!=EOF&&n)
    {
        for(i=0;i<n;i++)
        scanf("%d",&wei[i]);
        all=0;
        memset(next,-1,sizeof(next));
        memset(nkn,-1,sizeof(nkn));
        memset(skn,-1,sizeof(skn));
        memset(kal,0,sizeof(kal));
        memset(snkn,-1,sizeof(snkn));
        memset(dp,-1,sizeof(dp));
        for(i=0;i<n;i++)
        {
            scanf("%d",&fa[i]);
            if(fa[i]!=-1)
            insert(fa[i],i);
        }
        ans=0;
        for(i=0;i<n-1;i++)
        if(fa[i]==-1)
        {
            inidfs(i);
            ans+=kal[i];
        }
        inidfs(n-1);
        dfs(n-1);
        ans+=skn[n-1];
        printf("%d\n",ans);
    }
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值