[dp]拼题A打卡奖励 2021RoboCom复赛B

这篇博客讨论了一道关于在有限时间内完成多张打卡卷以获取最多金币的问题,将其转化为01背包问题。博主通过分析数据范围,提出了转换dp数组含义的方法,避免了朴素算法导致的超时问题。代码实现中,博主展示了如何初始化dp数组并进行状态转移,以找到在活动总时长内获得最大金币数的策略。
摘要由CSDN通过智能技术生成

拼题 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]);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值