题目翻译
JOI 君有
个装在手机上的挂饰,编号为
。 JOI 君可以将其中一些挂饰装在手机上。
JOI 君的挂饰有一些与众不同——其中的一些挂饰附有挂钩,你可以将其他挂饰挂在挂钩上。
号挂饰有
个挂钩。每个挂件要么直接挂在手机上,要么挂在其他挂件的挂钩上。直接挂在手机上的挂件最多有
个。
此外,每个挂件有一个安装时会获得的喜悦值,用一个整数来表示。如果 JOI 君很讨厌某个挂饰,那么这个挂饰的喜悦值就是一个负数。
JOI 君想要最大化所有挂饰的喜悦值之和。注意不必要将所有的挂钩都挂上挂饰,而且一个都不挂也是可以的。
输入格式
第一行一个整数
,代表挂饰的个数。
接下来
行,第
行有两个空格分隔的整数
和
,表示挂饰
有
个挂钩,安装后会获得
的喜悦值。
输出格式
输出一行,一个整数,表示手机上连接的挂饰总和的最大值。
样例
样例输入
5
0 4
2 -2
1 -1
0 1
0 3
样例输出
5
样例说明
挂饰 直接挂在手机上,然后将挂饰 和挂饰 分别挂在挂饰 的两个挂钩上,可以获得最大喜悦值 。
分析
首先,这显然是一道01背包问题。定义
为剩余挂饰
个能获得的最大价值。容易写出
的01板子,可这样会TLE。
不妨设想一下,对于第
个挂饰,如果之前有超过
个剩余挂钩,那么就算不用这个挂饰的挂钩,也不影响最后答案。
这样的话,第二重循环便只用循环到
,时间复杂度
。
这里还需要注意两个细节:
1.若
比
喜悦值大,比
挂钩少,若选
是正解,如果
在
前面,我们选的会是
,如果
在
前面,则不影响答案。
所以这里要用贪心的思想,将挂钩多的挂饰放在前面处理。
2.第二重循环为什么一个正序一个逆序,这个希望读者自行思考。(提示:一维数组01背包的性质)
代码
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <climits>
using namespace std;
const int MAXN = 2005, inn = INT_MIN;
struct Node {
int A, B;
}arr[MAXN * MAXN];
int dp[MAXN];
bool vis[MAXN];
bool cmp(Node x, Node y) {
return x.A > y.A;
}
int main() {
int n, maxx = 0;
scanf("%d", &n);
for(int i = 1; i <= n; i ++) {
scanf("%d%d", &arr[i].A, &arr[i].B);
}
sort(arr + 1, arr + 1 + n, cmp);//贪心
for(int i = 1; i <= n; i ++) dp[i] = inn;
dp[1] = 0; vis[1] = 1;
for(int i = 1; i <= n; i ++) {//01背包
if(arr[i].A > 0) {
for(int j = n; j >= 1; j --) {
if(!vis[j]) continue;
vis[min(j - 1 + arr[i].A, n)] |= vis[j];
dp[min(j - 1 + arr[i].A, n)] = max(dp[min(j - 1 + arr[i].A, n)], dp[j] + arr[i].B);
}
}
else {
for(int j = 1; j <= n; j ++) {
if(!vis[j]) continue;
vis[min(j - 1 + arr[i].A, n)] |= vis[j];
dp[min(j - 1 + arr[i].A, n)] = max(dp[min(j - 1 + arr[i].A, n)], dp[j] + arr[i].B);
}
}
}
for(int i = 0; i <= n; i ++) {
maxx = max(maxx, dp[i]);
}
printf("%d", maxx);
return 0;
}