拼题 A 的教超搞打卡活动,指定了 N 张打卡卷,第 i 张打卡卷需要 mi 分钟做完,完成后可获得 ci 枚奖励的金币。活动规定每张打卡卷最多只能做一次,并且不允许提前交卷。活动总时长为 M 分钟。请你算出最多可以赢得多少枚金币?
输入格式:
输入首先在第一行中给出两个正整数 N(≤10^3) 和 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 枚金币。
题意: 从n个物品中选出一个子集,在体积和不超过m的前提下价值最大,是一个标准的01背包问题。
分析: 虽然题目非常简单,但是数据范围是很坑的,n最大1e3,m最大5e5,如果直接跑朴素的01背包会超时,但是可以发现价值最高只有3e4,所以可以转换一下dp含义,设dp[i][j]表示考虑前i个物品,获得大于等于j价值的最小时间,这样状态转移方程就可以变成:
dp[i][j] = dp[i-1][j];
if(j >= v[i])
dp[i][j] = min(dp[i][j], dp[i-1][j-v[i]]+w[i]);
同时dp数组初始化要改变一下,我是手动初始化了n等于1的情况,因为这样更具实际含义,比较好想。最后结果还需要遍历一下dp数组,设sum为所有v[i]的和,从sum向下遍历,一旦出现了dp[n][i] <= m就停下,此时i就是最终答案。
具体代码如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <string>
using namespace std;
int n, m, w[1005], v[1005];
int dp[30005];//dp[i][j]表示前i个物品获得大于等于j价值的最小时间
signed main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
scanf("%d", &w[i]);
int sum = 0;
for(int i = 1; i <= n; i++){
scanf("%d", &v[i]);
sum += v[i];
}
for(int i = 1; i <= sum; i++){
if(i <= v[1]) dp[i] = w[1];
else dp[i] = 0x3f3f3f3f;
}
for(int i = 2; i <= n; i++){
for(int j = sum; j >= v[i]; j--)
dp[j] = min(dp[j], dp[j-v[i]]+w[i]);
}
int ans = sum;
while(dp[ans] > m) ans--;
cout << ans;
return 0;
dp[i][j] = dp[i-1][j];
if(j >= v[i])
dp[i][j] = min(dp[i][j], dp[i-1][j-v[i]]+w[i]);
}