01背包问题
二维表示
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1010;
int v[MAXN], val[MAXN], dp[MAXN][MAXN];
int main(){
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++) scanf("%d %d
", &v[i], &val[i]);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++){
//当前背包容量j装不下第i个物品
if(j < v[i])
dp[i][j] = dp[i - 1][j];
//可以装下第i个物品了,选出装或不装的最大值
//dp[i - 1][j - v[i]] + val[i]的意思是:由于当前的dp数组要依靠上
//一层的dp[i - 1][...],所以间接表示装下第i个物品后背包的最大价值
else
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + val[i]);
}
cout << dp[n][m];
return 0;
}
一维表示
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1010;
int v[MAXN], val[MAXN], dp[MAXN];
int main(){
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++) scanf("%d %d
", &v[i], &val[i]);
for(int i = 1; i <= n; i++)
for(int j = m; j >= 1; j--)
//删除第一个变量i后,dp[j] = dp[j],忽略
//dp[i][j] = dp[i - 1][j];
if(j >= v[i])
dp[j] = max(dp[j], dp[j - v[i]] + val[i]);
cout << dp[m];
return 0;
}
当二维优化成一维时,背包容量j从大到小的原因是:f[i, j]要用f[i - 1, j - v[i]] + w[i]来更新,从大到小可以保证算f[j]时用到的f[j - v[i]]存储的是f[i - 1, j - v[i]],而不是f[i, j - v[i]];如果从小到大循环,那么f[j - v[i]]会在f[j]前被计算出来,那么它就表示f[i, j - v[i]]了。
由此可以得出:背包问题中若用到上一层的状态时从大到小遍历,若用到本层的状态时从小到大遍历。
例题:子集背包问题
原问题链接:416. 分割等和子集
二维表示
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for(int a : nums) sum += a;
if(sum % 2 != 0) return false;
int n = nums.size();
sum /= 2;
//dp[i][j]表示前i个数组成的子集之和是否为j
vector<vector<bool>> dp(n + 1, vector<bool>(sum + 1, false));
for(int i = 0; i <= n; i++) dp[i][0] = true;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= sum; j++){
if(j < nums[i - 1])
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = dp[i - 1][j - nums[i - 1]] || dp[i - 1][j];
}
return dp[nums.size()][sum];
}
};
一维表示
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for(int a : nums) sum += a;
if(sum % 2 != 0) return false;
int n = nums.size();
sum /= 2;
vector<bool> dp(sum + 1, false);
dp[0] = true;
for(int i = 1; i <= n; i++)
for(int j = sum; j >= 1; j--)
if(j >= nums[i - 1])
dp[j] = dp[j - nums[i - 1]] || dp[j];
return dp[sum];
}
};
完全背包问题
常规背包问题
二维表示(三层for循环)
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1010;
int v[MAXN], val[MAXN], dp[MAXN][MAXN];
int main(){
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++)
scanf("%d %d
", &v[i], &val[i]);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
for(int k = 0; k * v[i] <= j; k++)
//这里的k = 0时,表示背包不选第i的物品的情况,此时dp[i][j] = dp[i - 1][j]
dp[i][j] = max(dp[i][j], dp[i - 1][j - k * v[i]] + k * val[i]);
cout << dp[n][m] << endl;
return 0;
}
二维表示(二层for循环)
f[i , j] = max( f[i - 1, j] , f[i - 1, j - v[i]] + w[i], f[i-1,j-2 * v[i]] + 2 * w[i], f[i - 1, j - 3 * v[i]]+3 * w[i] ...)
f[i , j - v[i]]= max( f[i - 1, j - v[i]], f[i-1,j-2 * v[i]] + w[i], f[i - 1, j - 2 * v[i]]+2 * w[i] ...)
当前背包容量 j 可以装下第 i 个物品时,则:f [ i ] [ j ] = max( f [ i - 1 ] [ j ] , f[ i ] [ j - v [ i ]] + w[i]),不断使用前面已经更新过的 f [ i ] [ ... < j ]来更新 f [ i ] [ j ],就像之前所说的,这里f [ i ] [ j ]用到了f [ i ] [ ...< j ],尚在同一层,所以从小到大遍历 j
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1010;
int v[MAXN], val[MAXN], dp[MAXN][MAXN];
int main(){
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++)
scanf("%d %d
", &v[i], &val[i]);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++){
if(j < v[i])
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = max(dp[i - 1][j], dp[i][j - v[i]] + val[i]);
}
cout << dp[n][m] << endl;
return 0;
}
一维表示
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int v[N], val[N], dp[N];
int main(){
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++)
scanf("%d %d
", &v[i], &val[i]);
for(int i = 1; i <= n; i++)
for(int j = v[i]; j <= m; j++)
dp[j] = max(dp[j], dp[j - v[i]] + val[i]);
cout << dp[m] << endl;
return 0;
}
例题:零钱兑换问题
原问题链接:518. 零钱兑换 Ⅱ
二维表示
class Solution {
public:
int change(int amount, vector<int>& coins) {
//dp[i][j]表示前i种硬币凑成总金额j的硬币组合数
int n = coins.size();
vector<vector<int>> dp(n + 1, vector<int>(amount + 1));
for(int i = 0; i <= n; i++) dp[i][0] = 1;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= amount; j++)
if(j < coins[i - 1])
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]];
return dp[n][amount];
}
};
一维表示
class Solution {
public:
int change(int amount, vector<int>& coins) {
//dp[i][j]表示前i种硬币凑成总金额j的硬币组合数
int n = coins.size();
vector<int> dp(amount + 1);
dp[0] = 1;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= amount; j++)
if(j >= coins[i - 1])
dp[j] = dp[j] + dp[j - coins[i - 1]];
return dp[amount];
}
};
多重背包问题
二维表示
可以看出和完全背包问题相差无几...
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 110;
int v[MAXN], val[MAXN], s[MAXN], dp[MAXN][MAXN];
int main(){
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++)
scanf("%d %d %d
", &v[i], &val[i], &s[i]);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
for(int k = 0; k <= s[i] && k * v[i] <= j; k++)
dp[i][j] = max(dp[i][j], dp[i - 1][j - k * v[i]] + k * val[i]);
cout << dp[n][m];
return 0;
}
一维表示
由于多重背包问题的状态涉及第 i 个物品的数量 s [ i ], 则 k 不能省略
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 110;
int v[MAXN], val[MAXN], s[MAXN], dp[MAXN];
int main(){
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++)
scanf("%d %d %d
", &v[i], &val[i], &s[i]);
for(int i = 1; i <= n; i++)
for(int j = m; j >= 1; j--)
for(int k = 0; k <= s[i] && k * v[i] <= j; k++)
dp[j] = max(dp[j], dp[j - k * v[i]] + k * val[i]);
cout << dp[m];
return 0;
}