「JOISC 2014 Day4」挂饰(背包DP)题解

题目翻译

JOI 君有 n n n 个装在手机上的挂饰,编号为 1 … n 1 \ldots n 1n。 JOI 君可以将其中一些挂饰装在手机上。
JOI 君的挂饰有一些与众不同——其中的一些挂饰附有挂钩,你可以将其他挂饰挂在挂钩上。 i i i 号挂饰有 A i A_i Ai 个挂钩。每个挂件要么直接挂在手机上,要么挂在其他挂件的挂钩上。直接挂在手机上的挂件最多有 1 1 1 个。
此外,每个挂件有一个安装时会获得的喜悦值,用一个整数来表示。如果 JOI 君很讨厌某个挂饰,那么这个挂饰的喜悦值就是一个负数。
JOI 君想要最大化所有挂饰的喜悦值之和。注意不必要将所有的挂钩都挂上挂饰,而且一个都不挂也是可以的。

输入格式

第一行一个整数 n n n,代表挂饰的个数。
接下来 n n n行,第 i i i 行有两个空格分隔的整数 A i A_i Ai B i B_i Bi,表示挂饰 i i i A i A_i Ai 个挂钩,安装后会获得 B i B_i Bi 的喜悦值。

输出格式

输出一行,一个整数,表示手机上连接的挂饰总和的最大值。

样例

样例输入
5
0 4
2 -2
1 -1
0 1
0 3
样例输出
5
样例说明

挂饰 2 2 2 直接挂在手机上,然后将挂饰 1 1 1 和挂饰 5 5 5 分别挂在挂饰 2 2 2 的两个挂钩上,可以获得最大喜悦值 4 − 2 + 3 = 5 4-2+3=5 42+3=5

分析

首先,这显然是一道01背包问题。定义 d p [ i ] dp[i] dp[i] 为剩余挂饰 i i i 个能获得的最大价值。容易写出 O ( n m ) O(nm) O(nm) 的01板子,可这样会TLE。
不妨设想一下,对于第 i i i 个挂饰,如果之前有超过 n n n 个剩余挂钩,那么就算不用这个挂饰的挂钩,也不影响最后答案。
这样的话,第二重循环便只用循环到 n n n ,时间复杂度 O ( n 2 ) O(n^2) O(n2)
这里还需要注意两个细节:
1.若 α \alpha α β \beta β 喜悦值大,比 β \beta β挂钩少,若选 β \beta β 是正解,如果 α \alpha α β \beta β 前面,我们选的会是 α \alpha α,如果 β \beta β α \alpha α 前面,则不影响答案。
所以这里要用贪心的思想,将挂钩多的挂饰放在前面处理。
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;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值