题目描述
JOI 君有 N N N 个装在手机上的挂饰,编号为 1 … N 1\dots N 1…N。 JOI 君可以将其中的一些装在手机上。
JOI 君的挂饰有一些与众不同——其中的一些挂饰附有可以挂其他挂件的挂钩。每个挂件要么直接挂在手机上,要么挂在其他挂件的挂钩上。直接挂在手机上的挂件最多有 1 1 1 个。
此外,每个挂件有一个安装时会获得的喜悦值,用一个整数来表示。如果 JOI 君很讨厌某个挂饰,那么这个挂饰的喜悦值就是一个负数。
JOI 君想要最大化所有挂饰的喜悦值之和。注意不必要将所有的挂钩都挂上挂饰,而且一个都不挂也是可以的。
1 ≤ N ≤ 2000 1\le N\le 2000 1≤N≤2000, 0 ≤ A i ≤ N ( 1 ≤ i ≤ N ) 0\le A_i\le N(1\le i\le N) 0≤Ai≤N(1≤i≤N), − 1 0 6 ≤ B i ≤ 1 0 6 ( 1 ≤ i ≤ N ) -10^6\le B_i\le 10^6(1\le i\le N) −106≤Bi≤106(1≤i≤N)。
算法分析
分类讨论每个挂件:
- 当没有挂钩且喜悦值为负时,显然不选它更优。
- 当没有挂钩且喜悦值为正时,显然在最终空闲的挂钩数一定的情况下尽可能优先选择这种条件下喜悦值最大的。
- 当有挂钩且喜悦值为负时,可以有选择和不选择两种情况。
- 当有挂钩且喜悦值为正时,显然选择它更优。
因此,我们直接选择所有有挂钩且喜悦值为正的挂件,排除所有没有挂钩且喜悦值为负的挂件。
对于有挂钩且喜悦值为负的挂件使用 0/1 背包 DP,设 f [ i ] [ j ] f[i][j] f[i][j] 为已经计算了有挂钩且喜悦值为负的挂件中的前 i i i 个,当前挂钩数为 j j j 时的最大喜悦值,可以使用一维数组优化空间复杂度。
设 s [ i ] s[i] s[i] 从大到小选择满足没有挂钩且喜悦值为正的前 i i i 个挂钩总喜悦度,则答案为 f [ n ] [ j ] + s [ j ] f[n][j]+s[j] f[n][j]+s[j] 的最大值。
注意 DP 的第二维要开两倍,因为有可能挂钩数已经差一点到达 2000 2000 2000,而剩下的挂件上的挂钩数都极大。
还有一种直接 DP 的做法,见 链接。
代码实现
#include <cstdio>
#include <cstring>
#include <algorithm>
const int maxn=4005;
int s[maxn],ssz=0,f[maxn];
struct type {int cnt,val;} e[maxn];int esz=0;
int main() {
int n;scanf("%d",&n);
int gcnt=1,gval=0;
for(int i=1;i<=n;++i) {
int A,B;scanf("%d%d",&A,&B);
if(A) {
if(B>=0) {gcnt+=A-1;gval+=B;}
else e[++esz]=(type){A,B};
}
else if(B>0) s[++ssz]=B;
}
std::sort(s+1,s+1+ssz);std::reverse(s+1,s+1+ssz);
for(int i=2;i<=4000;++i) s[i]+=s[i-1];
memset(f,0x80,sizeof(f));f[1]=0;f[gcnt>4000?4000:gcnt]=gval;
for(int i=1;i<=esz;++i)
for(int j=4000;j-e[i].cnt+1>=0;--j)
f[j]=std::max(f[j],f[j-e[i].cnt+1]+e[i].val);
int ans=0;
for(int i=0;i<=4000;++i) ans=std::max(ans,f[i]+s[i]);
printf("%d\n",ans);
return 0;
}