• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
jacklee404
Never Stop!
博客园    首页    新随笔    联系   管理    订阅  订阅
7-3 拼题A打卡奖励

7-3 拼题A打卡奖励

拼题 A 的教超搞打卡活动,指定了 N 张打卡卷,第 i 张打卡卷需要 mi 分钟做完,完成后可获得 ci 枚奖励的金币。活动规定每张打卡卷最多只能做一次,并且不允许提前交卷。活动总时长为 M 分钟。请你算出最多可以赢得多少枚金币?

输入格式:

输入首先在第一行中给出两个正整数 N(≤103) 和 M(≤365×24×60),分别对应打卡卷的数量和以“分钟”为单位的活动总时长(不超过一年)。随后一行给出 N 张打卡卷要花费的时间 mi(≤600),最后一行给出 N 张打卡卷对应的奖励金币数量 ci(≤30)。上述均为正整数,一行内的数字以空格分隔。

输出格式:

在一行中输出最多可以赢得的金币数量。

输入样例:

5 110
70 10 20 50 60
28 1 6 18 22

输出样例:

40

样例解释:

选择最后两张卷子,可以在 50+60=110 分钟内获得 18+22=40 枚金币。

思路

​ 普通背包的话,\(dp[i][j]\)表示考虑前\(i\)个打卡卷,当时间为j时的最大价值,时间总长大概为5e4,而打卡卷数量为1e2, 复杂度上界\(\Theta(n^2)\) ≈ \(5 \times 10^{7}\) 会超时

​ 我们可以换个思路,设\(dp[i][j]\)表示考虑前i个打卡卷,当价值为j时的最小时间,那么有以下等式(w[i]表示价值,v[i]表示容量):

\(dp[i][j] = max(dp[i-1][j], dp[i][j-w[i]] + v[i])\)

值得注意的地方:

  1. 考虑j的合法范围,应满足\(w[i] \le j \le tot\), 对于第i次循环(是否考虑将i放进背包)

  2. 考虑数组存不下,应该使用滚动数组,保证\(j-w[i]\)应该为\(i\)层的状态,所以我们从后向前枚举

  3. dp数组不满足二段性在求的时候不可以二分找到最大价值,应考虑从最大奖励开始枚举,枚举到第一个便是答案

Code

#include <iostream>
#include <cstring>
using namespace std;

const int M = 525600, N = 3e4 + 10;

// dp[i][j] 为考虑前i个打卡卷,所需时间为j时的最大奖励数量 dp 物品和容量

// dp[i][j] dp 物品和价值
// dp[i][j] 表示考虑前i个物品,总价值为j时的最小时间
// dp[i][j] = min(dp[i-1][j], d[i][j-w[i]] + v[i])
int v[N], w[N], dp[N];

int main() {
	int n, m, ans, tot = 0; cin >> n >> m;
	for (int i = 1; i <= n; i ++) cin >> v[i];
	for (int j = 1; j <= n; j ++) cin >> w[j], tot += w[j];
	memset(dp, 0x3f, sizeof dp);
	dp[0] = 0;
	for (int i = 1; i <= n; i ++) {
		for (int j = tot; j >= w[i]; j --) {
			dp[j] = min(dp[j], dp[j-w[i]] + v[i]);
		}
	}
	int k = 1;
	while (dp[tot] > m) tot --;
	cout << tot;
}
posted on 2023-01-03 22:31  Jack404  阅读(223)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3