一、先温习一下01背包问题
有N件物品和一个容量为V的背包。第i件物品的体积是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
条件汇总
--------
背包限制容量:Z
此时背包容量:C
物品:1 , i ... n //代表编号
重量:wight[1] , wight[i] ... wight[n]
价值:value[1] , value[i] ... value[n]
目标:在wight总和小于Z的情况下,使得value的总和最大
记录已计算好的结果用数组dp表示
dp[i][j]的含义是:从下标为[0至i]的物品⾥任意取,背包容量为j时,价值总和最⼤是多少(一个数字)。
逻辑依据
从这个题目中可以看出,01背包的特点就是:每种物品仅有一件,可以选择放或不放。
- 确定最优子结构(不用重复的东西确定是没问题的)
- 状态转移方程(更好的结果就更新)
01背包问题的状态转移方程:
dp[i][wight] = max{
dp[i-1][C] ,
dp[i-1][C-wight[i]]+value[i]
}
dp[i-1][C]代表的就是不将这件物品放入背包后的总价值,
dp[i-1][C-wight[i]]+value[i]则是代表将第i件放入背包之后的总价值
然后就是遍历执行比对,以数组f存储空间换取执行时间速度避免重复。
for (i = 1; i <= n; i++)
for (j = 1; j <= Z; j--)
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - wight[i]] + value[i]);
执行完了一个如同九九乘法表的存储最优配置的数组就生成了,答案靠查表(让C==Z时的那一列的最后一行的值)就行了~
详细步骤
样例是:
5种物品,最大重量5
物品(价值,重量)依次为:【(1,1);(3,2);(5,6);(4,5);(6,4)】
方便研究过程的代码
#include <iostream>
#include <cstring>
#include <algorithm>//min、max函数
using namespace std;
const int N = 6;//0~5
int dp[N][N];
void paintDP()
{
cout<< "===============" << endl;
for (int i = 0; i < N; i ++)
{
for (int j = 0; j < N; j++)
{
cout << dp[i][j] << "\t";
}
cout << endl;
}
cout<< "===============" << endl;
system("pause");
}
/*
输入样例
5 5
1 3 5 4 6
1 2 6 5 4
*/
int main()
{
system("chcp 65001");//解决utf8中文乱码,另外936代表GBK;
dp[N][N] = {0};//表格初始化
int value[N] = {0};
int weight[N] = {0};
int n,Z;
cin >> n >> Z;
if(n>=N){cout << "行计数物类超限";system("pause"); return 0;}
if(Z>=N){cout << "列计数重量超限";system("pause"); return 0;}
//商品编号从1开始
//每个商品的价格
for (int i = 1; i <= n; i ++)
{
cin >> value[i];
}
//每个商品的重量
for (int i = 1; i <= n; i ++)
{
cin >> weight[i];
}
paintDP();
//生成动态规划表
for (int i = 1; i <= n; i ++)
{
cout << "开始考虑第" << i << "种物品" << endl ;
//遍历
for (int j = 1; j <= Z; j++)//矩阵的列是重量,行是物品
{
//上一层的最大值,和上一层减去当前物品重量的【最大值】加上当前物品价值的最大值
if(j>=weight[i])//否则数组下标就又可能访问上上层失控了!
{
dp[i][j] = max(
//不要i这个物品
dp[i - 1][j],
//要i这个物品的价值(那个减号是找之前的,没这个重量时的最大值)
dp[i - 1][j - weight[i]] + value[i]
);
//vscode 里,监视二维数组dp这么写 *(int(*)[6])dp@6
}
else dp[i][j]=dp[i - 1][j];
}
paintDP();
}
system("pause");
return 0;
}
二、动态规划策略
在搜索中会重复计算一些结点,所以,如果我们把搜索过程中计算过的结点的值记录下来(前提是最优子结构)以保证不重复计算的话,速度就会提高很多。属于是“以内存空间换处理时间”了。
三、题解
这个题倒着推满足条件时的最大价值时多少,在推的过程中记录,就可以省去生成表格后再找的麻烦。
#include <iostream>
#include <cstring>
#include <algorithm>//min函数
using namespace std;
const int N = 35, M = 3e5 + 10;
int f[N][M];
int a[N];
int main()
{
int n, x;
cin >> n >> x;
int sum = 0;
for (int i = 1; i <= n; i ++)
{
cin >> a[i];
sum += a[i];//累加总价值
}
for (int i = 1; i <= n; i ++) f[i][sum] = sum;//最后一列,价值给满
for (int j = x; j <= sum; j ++) f[0][j] = sum;//第一行,价值给满
//逆着求动态规划表
int res = 3000010;
for (int i = 1; i <= n; i ++)
{
for (int j = sum; j >= x; j --)//遍历试着往下减
{
f[i][j] = f[i - 1][j];
if (j + a[i] <= sum)//价值更小的话,就更新
f[i][j] = min(f[i][j], f[i - 1][j + a[i]] - a[i]);
if (f[i][j] >= x) res = min(res, f[i][j]);//小于x包邮的情况下,找到更小的花费(因为达到后就不再更新res了)
}
}
cout << res;
system("pause");
return 0;
}
代码参考:202209-2 CCF 何以包邮? (01背包解法(两种解法) + 详细思路)_一只可爱的小猴子的博客-CSDN博客