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

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

文章目录
    • 干货
    • 例题:
      • L3-2 拼题A打卡奖励 (30 分)
        • 题目描述
        • 知识点
        • 思路
        • 解析说明
        • 代码一(19分)
        • 代码二(30分满分)
    • 参考链接

干货

常见的动态规划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 枚金币。

知识点
  • 一维数组实现dp
  • fill的用法:C++ 中 fill() 的使用
  • 特殊的0-1背包
思路

显然这是一个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 
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<
//	}
	cout<<dp[m];
	
}
代码二(30分满分)
//按照原来做法就是用最小容量求最大价值,
//现在反过来用最大价值求最小容量。最大价值:1000*30, 复杂度:1000*30*1000 。
//求获得对应价值的最小需要时间 
#include 
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<
	
}
参考链接

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

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/676243.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-19
下一篇 2022-04-19

发表评论

登录后才能评论

评论列表(0条)

保存