第一次听到动态规划时还是一年前,对于算法都不是太熟的我来说,每次遇到动态规划问题都只能绕着走。可能会有人问为什么不去看题解尝试解决它,因为我连最基本的背包问题都搞不明白,即使对着题解,当时解决了它。可很快就会忘记,当你下次再遇到相似问题,也只会留下浅浅的印象,根本达不到能解决同类问题的效果。正如你吃的快,饿的也快。
期间我也看过不少文章和题解,可终究难以理解那短短的两行核心代码,直到我看了用表举例的视频对着状态转移方程,才理解了背包问题。
背包问题的核心就是找出数组元素之间的关系式
step1:定义一个二维数组dp[m][n]和两个一维数组v[m],w[m]
dp[i][j]:容量为j的背包装入前i个物品时的最大价值(注意这里的j代表实时总容量,i代表石头个数,可以选全部,也可以选部分,只要满足价值最大即可)
v[i]: 第i个物品所占体积
w[i]:第i个物品的价值
step2 :找数组间关系
对于问题的理解:不仅要能装下石头,还要找一定容量下石头价值总和最大的装法
首先,我们考虑装不下的情况
dp[i][j]=dp[i-1][j];
接着,考虑装得下的情况,要想装下肯定要保证总容量j>=v[i](这里的总容量是遍历时赋予的,而不是装入一些石头后剩下的总容量抑或一开始给定的总容量)
dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i])这乍看肯定费解,为什么要用这取两个状态最大值,这两个比较值分别又是什么意思等问题,我刚开始也是,怎么想也搞不明白,下面我们结合表格讲解一下。
具体题目:给定一个容量为8的背包
再给出四块石头的体积与价值
编号 | 体积 | 重量 |
1 | 2 | 3 |
2 | 3 | 4 |
3 | 4 | 5 |
4 | 5 | 6 |
求解该背包可装石头的最大价值
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | ||||||||
2 | 0 | ||||||||
3 | 0 | ||||||||
4 | 0 |
这里分别从石头个数为0以及背包容量为0两方面去考虑,所以得到的全是0
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
2 | 0 | ||||||||
3 | 0 | ||||||||
4 | 0 |
接着从(1,1)考虑到(1,8)i保持1表示不管你背包容量多大都只有装和不装这两种选择,这就取决于背包容量能否装下,以图中标记的颜色为例
对于(1,1)这个位置表示容量为1的背包装不下第一块体积为2的石头
对应代码: dp[i][j]=dp[i-1][j];(像第一排第一列即使为0,也依然代表着那个位置的最优情况)
对于(1,2)这个位置表示容量为2的背包可以装第一块石头,这里举例不具代表性,所以直接给出结果石头价值3。
后面只要遍历时给定的总容量j大于第一块石头的体积的都同(1,2)情况相似。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
2 | 0 | 0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 |
3 | 0 | ||||||||
4 | 0 |
这里依旧举例说明
到这行,我们比起i=1时,就变成对石头2的选择,不过最后的价值要做比较后决定。
(2,1)0表示容量为1的背包无法装入第二块体积为3的石块。
(2,3)4从这开始就到了问题的核心部分,(2,3)表示总容量为3的背包可以
通过比较遍历给定的总容量j和第二块石头体积发现可以装入第二块体积为3的石头,再通过比较装入这块石头的状态(对应:dp[i-1][j-v[i]]+w[i]去掉选择加入第二块石头时的最优解,这时它只能回到挑选第一块石头的那行,且要保证预留空间恰好为本次选择石头二的体积,只有这样,才能保证图中标红的最佳价值加上第二块石头价值后是本状态最大价值)与上一个状态(指容量相同时,选择石头的权利变少,对应3的最大价值),给出(2,3)时的最大价值。可能有人会问这不明摆着加入后价值最大还用得着比吗?这只是在本题中是这种情况或者下意识认为就应该这样。如果我们这里把第二块石头的价值变为小于第一块石头的任意价值,是不是就有比的必要了呢?
这是开头给出的状态转移方程: dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i])
再去结合上面这段话理解一下。
#include<iostream>
using namespace std;
int dp[102][1002];
int t[102];
int v[102];
int main()
{
int T,M,res;
cin >> T >> M;
for (int i = 1; i <= M; i++)
cin >> t[i] >> v[i];
for (int i = 1; i <= M; i++)
for (int j = 1; j <= T; j++)
{
dp[i][j] = dp[i-1][j];
if (j >= t[i])
dp[i][j] = max(dp[i-1][j],dp[i-1][j-t[i]]+v[i]);
}
for (int i = 0; i <= 4; i++)//针对本题输入,平时需去掉这块
{
for(int j = 0; j <=8 ; j++)
cout << dp[i][j] << " ";
cout << endl;
}
cout << dp[M][T] << endl;
return 0;
}
输入:8 4
2 3
3 4
4 5
5 6
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
2 | 0 | 0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 |
3 | 0 | 0 | 3 | 4 | 4 | 7 | 8 | 9 | 9 |
4 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 10 |
还可以思考下,为什么石头选取不重复,那样价值不是更高问题
此外,本代码空间复杂度有待优化,等下回有机会写一下优化。
不懂得小伙伴可以看此链接,我就是通过这个视频明白背包问题的。