目录
一、完全背包
完全背包可以描述为:有n件物品和一个最多能背体积为 v 的背包。第 i 件物品的体积是 v[i],得到的价值是value[i] 。每件物品有无限多个,求解将哪些物品装入背包里物品价值总和最大。
在完全背包问题中,对于每件物品,有多种状态:不选,选一个,选两个......
题目
题目解析
第一问的所有情况就是上图红色方框的内容, 第二问的所有情况就是上图蓝色方框的内容。
解决第一问
第一步:确定状态表示
dp[i][j]:表示从前 i 个物品中挑选,总体积不超过 j 的所有选法中,能挑选出来的物品价值最大值。
第二步:推出状态转移方程
前面我们已经说明了,完全背包中的每件物品的数量有无限个,所以对于每件物品来说,有选或者选1个,选2个...... 多种状态。根据最后一步的状况,分情况讨论,即要么不选择 i 号物品,要么选 1个 i 号物品,要么选 2个 i 号物品,要么选 3个 i 号物品......
选一个 i 物品,这时就有一个v[i]的体积了,然后仅需去 1 ~ i - 1 区间选不超过 j - v[i] 的最大价值,然后再加上 i 的价值。
选两个 i 物品,这时就有两个v[i]的体积了,然后仅需去 1 ~ i - 1 区间选不超过 j - 2*v[i] 的最大价值,然后再加上两个 i 的价值。
选n个 i 物品,这时就有n个v[i]的体积了,然后仅需去 1 ~ i - 1 区间选不超过 j - n*v[i] 的最大价值,然后再加上n个 i 的价值。
第三步:初始化dp表
第一列不需要初始化。
第一行表示没有物品,当 j 为0的时候,要凑成不超过 j,只要不选就行了,最大价值是0。如果j为1、2、3......但是没有物品可选,所以最大价值也是0。
解决第二问
第一步:确定状态表示
dp[i][j]:表示从前 i 个物品中挑选,总体积恰好为 j 的所有选法中,能挑选出来的物品价值最大值。
第二步:推出状态转移方程
状态转移方程的推导方法和第一问相同,只是有一些细节问题需要注意。
题目要求选出的物品中,总体积要恰好为 j ,那么就有可能怎么选也凑不出物品的总体积恰好为 j 的情况,那么我们就不能使用这种情况,可以用 dp[i][j] == -1 来表示凑不出物品的总体积恰好为 j 的情况,即这种情况不存在。
第三步:初始化dp表
第一列不需要初始化。
第一行表示没有物品,当 j 为0的时候,要凑成不超过 j,只要不选就行了,最大价值是0。如果j为1、2、3......因为没有物品可选,所以这些情况都不存在,值为-1。
解题代码:
cpp
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int n, V;
cin >> n >> V;
vector<int> v(n);
vector<int> w(n);
for(int i = 0; i < n; i++)
cin >> v[i] >> w[i];
vector<vector<int>> dp1(n+1, vector<int>(V+1));
for(int i = 1; i <= n; i++)
{
for(int j = 0; j <= V; j++)
{
dp1[i][j] = dp1[i-1][j];
if(j >= v[i-1])
{
int ret = max(dp1[i-1][j], dp1[i][j-v[i-1]] + w[i-1]);
dp1[i][j] = max(dp1[i][j], ret);
}
}
}
vector<vector<int>> dp2(n+1, vector<int>(V+1));
for(int i = 1; i <= V; i++)
dp2[0][i] = -1; // 用-1来表示该情况不存在
for(int i = 1; i <= n; i++)
{
for(int j = 0; j <= V; j++)
{
dp2[i][j] = dp2[i-1][j];
if(j >= v[i-1] && dp2[i][j-v[i-1]] != -1)
{
int ret = max(dp2[i-1][j], dp2[i][j-v[i-1]] + w[i-1]);
dp2[i][j] = max(dp2[i][j], ret);
}
}
}
cout << dp1[n][V] << endl;
cout << (dp2[n][V] == -1 ? 0 : dp2[n][V]) << endl;
return 0;
}
二、零钱兑换
第一步:确定状态表示
dp[i][j]:表示从前 i 个硬币中挑选,使其总和恰好等于j,所有选法中,最少的硬币个数。
第二步:推出状态转移方程
第三步:初始化dp表
第一列不用初始化。第一行表示没有硬币,当 j 为0表示硬币总和为0,不选就行了。
解题代码:
cpp
class Solution
{
public:
int coinChange(vector<int>& coins, int amount)
{
int m = coins.size();
vector<vector<int>> dp(m+1, vector<int>(amount+1));
for(int i = 1; i <= amount; i++)
dp[0][i] = 0x3f3f3f3f;
for(int i = 1; i <= m; i++)
{
for(int j = 0; j <= amount; j++)
{
dp[i][j] = dp[i-1][j];
if(j >= coins[i-1])
dp[i][j] = min(dp[i][j], dp[i][j-coins[i-1]]+1);
}
}
return dp[m][amount] == 0x3f3f3f3f ? -1 : dp[m][amount];
}
};
三、零钱兑换II
第一步:确定状态表示
dp[i][j]:表示从前 i 个硬币中挑选,使其总和恰好等于j,一共有多少种选法。
第二步:推出状态转移方程
第三步:初始化dp表
第一列不用初始化。
第一行表示没有硬币,当 j = 0时表示总和为0,不选就行了,这是一种选法。当j = 1、2、3...,因为没有硬币,所以根本凑不出总和等于j的选法。直接都给0就行了。
解题代码:
cpp
class Solution
{
public:
int change(int amount, vector<int>& coins)
{
int m = coins.size();
vector<vector<double>> dp(m+1, vector<double>(amount+1));
dp[0][0] = 1;
for(int i = 1; i <= m; i++)
{
for(int j = 0; j <= amount; j++)
{
dp[i][j] += dp[i-1][j];
if(j >= coins[i-1])
dp[i][j] += dp[i][j-coins[i-1]];
}
}
return dp[m][amount];
}
};
四、完全平方数
第一步:确定状态表示
dp[i][j]:表示从前 i 个完全平方数种挑选,使其总和恰好等于 j,所有选法中,平方数的最少数量。
第二步:推出状态转移方程
第三步:初始化dp表
第一列不用初始化。
第一行表示完全平方数为0,当 j = 0表示总和为0,不选就行了,最少数量为0。当 j = 1、2...时完全平方数为0,根本凑不出和为 j,然后我们填dp[i][j]要最小值,为了不让这些位置得值影响填表,因此可以给0x3f3f3f3f。
解题代码:
cpp
class Solution
{
public:
int numSquares(int n)
{
int m = sqrt(n);
vector<vector<int>> dp(m+1, vector<int>(n+1));
for(int i = 1; i <= n; i++)
dp[0][i] = 0x3f3f3f3f;
for(int i = 1; i <= m; i++)
{
for(int j = 0; j <= n; j++)
{
dp[i][j] = dp[i-1][j];
if(j >= i*i)
dp[i][j] = min(dp[i][j], dp[i][j-i*i]+1);
}
}
return dp[m][n];
}
};