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(NP)
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();
}