【JZOJ5661】【GDOI2018Day1模拟4.17】药香沁鼻(树形背包)

Problem

  还剩P点能量值的小C要用N朵花煮药,每朵花i有三个信息:
1. 摘下它需要花Wi点能量;
2. 有一朵钦慕的花fi(保证1≤fi≤max(1,i-1)),煮药时,若fi不在药中,则i消失,然后钦慕i的花也会消失,以此类推;
3. 若在药中且没有消失,则产生vi点贡献。
  求最大贡献。

Hint

这里写图片描述

Solution

  这道题我刚看以为直接按拓扑序DP,然后没管了,滚去看后面的题。但是当我打到一半时才发现不对。
  首先我们设dp[i][j]表示选到第i朵花时,还剩j点能量的最大贡献。那么显然按拓扑序DP。
  但是普通的拓扑序DP只能从一个点经过一条出边转移到另一个点,而此题不一样。比如当N=6,P=9,w={1,1,8,1,2,4},f={1,1,1,3,3,1},v={1,100,1,6,1000,4}时,显然选1,2,6是最优解。而我们若已从点1转移到点2,那不可能再去转移到点6;但最优解就是要转移给点2、点6两个点。
  当时我打到一半,而且打的是类似树形DP的dfs转移,便灵光一闪:我们先从x经过一条出边转移到某个子节点y,然后继续在以y为根的子树里递归;然后从y弹回到x的时候,我们可以使x的背包与y的背包的那些贡献求个max,使x的背包之中的贡献更大一些。这样子的话,我们再从x转移到其他子节点时,我们就可能已经转移过y(当然你也可以不转移,反正是使x和y的背包求max)。这就相当于多路转移。
  时间复杂度: O ( N P )

Code

#include <cstdio>
#include <algorithm>
using namespace std;
#define fo(i,a,b) for(i=a;i<=b;i++)
const int N=5e3+2,P=1e4+1;
int i,n,p,w[N],f[N],v[N],next[N],last[N],dp[N][P],ans;
bool bz[N][P];

inline void insert(int x,int y)
{
    next[i]=last[x];
    last[x]=i;  
}
void scan()
{
    scanf("%d%d",&n,&p);
    fo(i,1,n)scanf("%d%d%d",&w[i],&f[i],&v[i]),insert(f[i]==i?0:f[i],i);
}

void trans(int x,int y)
{
    fo(i,w[y],p)
        if(bz[x][i])
        {
            bz[y][i-w[y]]=1;
            dp[y][i-w[y]]=dp[x][i]+v[y];
        }
}
void clone(int x,int y)
{
    fo(i,0,p)
    {
        bz[x][i]|=bz[y][i];
        dp[x][i]=max(dp[x][i],dp[y][i]);
    }
}
void DP(int x)
{
    for(int y=last[x];y;y=next[y])
    {
        trans(x,y);
        DP(y);
        clone(x,y);
    }
}

void work()
{
    fo(i,0,p)ans=max(ans,dp[0][i]);
    printf("%d",ans);
}

int main()
{
    freopen("medicine.in","r",stdin);
    freopen("medicine.out","w",stdout);
    scan();

    bz[0][p]=1;
    DP(0);

    work();
}

猜你喜欢

转载自blog.csdn.net/qq_36551189/article/details/80002847