- 干货
- 例题:
- 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的双重循环, 空间优化版本的内层循环必须是逆序的.
如果这一点理解了, 整个程序的实现就非常容易了.
拼题 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,即为所需结果。
//按照常规的想法,把时间看成是物品的体积,金币看成是价值,则 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背包问题详解(浅显易懂)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)