01背包问题使用一维数组对空间开销进行优化及例题解析

干货

常见的动态规划0-1背包问题使用二维数组来实现
最常见的状态转移方程为:dp[i][w]=max{dp[i-1][w],dp[i-1][w-wi]+vi}
代码实现如下:

for (int i = 1; i <= n; i++)
{
	for (int j = 0; j <=V; j++)
	{
		if (j >= w[i]) //如果背包装得下当前的物体
		f[i][j] = max(f[i - 1][j], f[i - 1][j - w[i]] + v[i]);
		else f[i][j] = f[i - 1][j];//如果背包装不下当前物体
	}
}

但这种方法下,若容量的值过大很容易导致二维数组空间过大。
因此,采用以下空间优化版本的0-1背包问题解法,使用一维数组存储数据,可进行空间上的优化!
这种方法的状态转移方程为:dp[w]=max{dp[w], dp[w-wi]+vi}
代码实现如下:

for (int i = 1; i <= n; i++)
{
	for (int j = V; j >= w[i]; j--)//逆序!!
	{
		f[j] = max(f[j], f[j - w[i]] + v[i]);
	}
}

理解起来, 和二维数组实现是差不多的.
但是, 在具体的实现层面上, 有一个很反直觉的点:
不同于二维dp的双重循环, 空间优化版本的内层循环必须是逆序的.
如果这一点理解了, 整个程序的实现就非常容易了.

例题:

L3-2 拼题A打卡奖励 (30 分)

题目描述

拼题 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 枚金币。

知识点
思路

显然这是一个01背包问题。但是,按照常规的想法,把时间看成是物品的体积,金币看成是价值,则 M 就是背包的容量。但是根据题目的数据范围, N(≤1000)M(≤365×24×60) ,复杂度 NM 肯定会TLE。

我们再看一下题目给定其他数据范围,显然 ci<30 成为题目转化的一个突破口。按照原来做法就是用最小容量求最大价值,现在反过来用最大价值求最小容量。最大价值:1000∗30, 复杂度:1000∗30∗1000 。

解析说明
  • 使用二维数组的局限:在此题中可将时间看成0-1背包中的“体积”,金币看做价值,但如果使用二维数组来解答的话,时间的最大值为365×24×60,而物品最大值为1000,因此最大的情况下,二维数组最少要开a[1000][365×24×60],会超空间,因此考虑用一维数组实现0-1背包。
  • 代码一说明:代码一中是以时间为下标,dp[i]中,i表示时间,dp[i]表示该时间能获得的最大金币。但是根据题目的数据范围, N(≤1000)M(≤365×24×60) ,复杂度 NM 肯定会TLE。这种方法只有19分。
  • 代码二说明:代码二中是以金币为下标,dp[i]中,i表示金币数,dp[i]表示获得该金币数所需的最少时间。现在反过来用最大价值求最小容量(求获得对应价值的最小需要时间 )。最大价值:100030, 复杂度:100030*1000 。这种方法下不会超时,顺利过了所有点,30分。注意最后是从大到小遍历,当dp[i](时间)小于题目要求的最大时间时,即可输出此时对于的i(金币数)后return,即为所需结果。
代码一(19分)
//按照常规的想法,把时间看成是物品的体积,金币看成是价值,则 M 就是背包的容量。
//但是根据题目的数据范围, M(≤365×24×60)M(≤365×24×60) ,复杂度 NM 肯定会TLE。
//19分  
#include <bits/stdc++.h>
using namespace std;
const int M = 365*24*60+10,N=1010;
int t[N],w[N];
int dp[M];
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>t[i];
	}
	for(int i=1;i<=n;i++){
		cin>>w[i];
	}
	for(int i=1;i<=n;i++){
		for(int j=m;j>=t[i];j--){
			dp[j]=max(dp[j],dp[j-t[i]]+w[i]);
		}
	}
//	for(int i=1;i<=m;i++){
//		cout<<dp[i]<<" ";
//	}
	cout<<dp[m];
	
}
代码二(30分满分)
//按照原来做法就是用最小容量求最大价值,
//现在反过来用最大价值求最小容量。最大价值:1000*30, 复杂度:1000*30*1000 。
//求获得对应价值的最小需要时间 
#include <bits/stdc++.h>
using namespace std;
const int MaxGold=30050,N=1010;
const int M = 365*24*60+10;
int t[N],w[N];
int dp[MaxGold];
int main(){
	int n,m;
	//注意要先初始化为大值后再用dp
	fill(dp,dp+MaxGold,M);//fill的用法
	dp[0]=0;//!!!!
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>t[i];
	}
	int sum=0;
	for(int i=1;i<=n;i++){
		cin>>w[i];
		sum+=w[i]; 
	}
	for(int i=1;i<=n;i++){
		for(int j=sum;j>=w[i];j--){
//			j:金币数量,dp[j]:获得该数量金币所需的最小时间 
			dp[j]=min(dp[j],dp[j-w[i]]+t[i]);//注意求得是最小时间,所以是min
		}
	}
	for(int i=sum;i>=0;i--){
		if(dp[i]<=m)
		{
			cout<<i;
			return 0;
		}
	}
//	cout<<dp[m];
	
}

参考链接

7-2 拼题A打卡奖励 (25 分) 特殊的01背包
01背包问题及一维数组优化
01背包问题详解(浅显易懂)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GCTTTTTT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值