1. 0/1背包
n
个物品,每个物品有一个重量 v[i]
和一个价值 w[i]
。背包的最大承重为 m
。每个物品只能使用一次(0/1意味着要么不选这个物品,要么完全选这个物品)。
- 自底向上与制表递推
int w[N], v[N]; // 价值、重量
int dp[N][N];
int solve(int n, int m) // n件物品、m总体积
{
for (int i = 1; i <= n; i++)
{
for (int j = 0; 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 - 1][j - v[i]] + w[i]);
}
}
return dp[n][m];
}
- 自顶向下与记忆化
int w[N], v[N]; // 价值、重量
int dp[N][N];
int solve(int i, int j)
{
if (dp[i][j] != 0)
return dp[i][j];
if (i == 0)
return 0;
int res;
if (j < v[i])
res = solve(i - 1, j);
else
res = max(solve(i - 1, j), solve(i - 1, j - v[i]) + w[i]);
return dp[i][j] = res;
}
- 滚动数组
1.交替滚动
int dp[2][N];
int solve(int n, int m) // n件物品、m总重量
{
int now = 0, old = 1;
for (int i = 1; i <= n; i++)
{
swap(now, old);
for (int j = 0; j <= m; j++)
{
if (j < v[i])
dp[now][j] = dp[old][j];
else
dp[now][j] = max(dp[old][j], dp[old][j - v[i]] + w[i]);
}
}
return dp[now][m];
}
2.自我滚动
int dp[N];
int solve(int n, int m) // n件物品、m总重量
{
for (int i = 1; i <= n; i++)
{
for (int j = m; j >= v[i]; j--)
{
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
}
}
return dp[m];
}
2.分组背包
假设我们有n
组物品,每组物品的数量为s[i]
,但在同一组内的物品冲突,因此每组最多只能选择一件物品,每组的物品有自己的重量v[i][j]
和价值w[i][j]
。我们需要从这些物品中选择一些放入背包中,使得背包中物品的总价值最大,同时不超过背包的最大承重m
。
分组背包问题就是在01背包问题的基础之上,多了一个在每个组中选出最优的那个物品(或者不选)。
中层循环是从背包容量m到0的逆序遍历,这是为了防止同一个组中的物品被重复放入背包(即保证每组中至多选择一个物品)
int w[N][N], v[N][N], s[N]; // w价值、v重量
int dp[N];
int n, m;
int solve(int n, int m) //n组物品、m总容量、每组物品s[i]个
{
for (int i = 1; i <= n; i++)
{
for (int j = m; j >= 0; j--)
{
for (int k = 1; k <= s[i]; k++) // 遍历第i组中的每个物品
{
if (j >= v[i][k])
dp[j] = max(dp[j], dp[j - v[i][k]] + w[i][k]);
}
}
}
return dp[m];
}
3.完全背包
完全背包问题是背包问题的一种变体。与0/1背包问题不同的是,在完全背包问题中,每种物品都有无限多个可用,这意味着你可以选择任意数量的同一种物品,只要不超过背包的容量限制
- 朴素写法
int w[N], v[N]; // w价值、v重量
int dp[N][N];
int solve(int n, int m) //n组物品、m总容量
{
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= m; j++)
{
for (int k = 0; k * v[i] <= j; k++)
{
dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i] * k] + w[i] * k);
}
}
}
return dp[n][m];
}
- 优化一
int w[N], v[N]; // w价值、v重量
int dp[N][N];
int solve(int n, int m) // n组物品、m总容量
{
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= m; j++)
{
if (j < v[i])
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = max(dp[i][j], dp[i][j - v[i]] + w[i]);
}
}
return dp[n][m];
}
- 优化二
int w[N], v[N]; // w价值、v重量
int dp[N];
int solve(int n, int m) // n组物品、m总容量
{
for (int i = 1; i <= n; i++)
for (int j = v[i]; j <= m; j++)
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
return dp[m];
}
4.多重背包
多重背包问题(也称为有数量限制的背包问题)是背包问题的一个变种。在这种情况下,每种物品都有一个数量限制,即每种物品最多可以选取多少个。这介于0/1背包问题(每种物品只能选一次)和完全背包问题(每种物品可以无限次选取)之间。
问题定义
有n
组物品,每组物品有s[i]
个,每种物品的重量为v[i]
、价值为w[i]
。需要从这些物品中选择一些放入背包中,使得背包中物品的总价值最大,同时不超过背包的最大承重m
。
1. 直接展开法
这种方法直接将每种物品的所有可能性展开,例如,如果某种物品的数量上限是5,则可以构造出5个虚拟物品(0个、1个、2个、3个、4个和5个),然后将问题转化为0/1背包问题。
2. 平滑化处理
这种方法通过对物品进行预处理,减少状态转移次数,从而提高效率。具体来说,可以将数量限制较大的物品进行平滑化处理,将其转换为几个子问题。
平滑化处理的步骤
- 预处理阶段:对于每种物品,如果数量上限较大,可以将其分解成多个子物品。
- 状态转移:利用预处理后的物品进行动态规划的状态转移。
- 一般方法
int w[N], v[N], s[N]; // w价值、v重量
int dp[N][N];
int solve(int n, int m) // n组物品、m总容量
{
for (int i = 1; i <= n; i++)
{
for (int j = 0; 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 - v[i] * k] + w[i] * k);
}
}
}
return dp[n][m];
}
- 滚动数组
int w[N], v[N], s[N]; // w价值、v重量
int dp[N];
int solve(int n, int m) // n组物品、m总容量
{
for (int i = 1; i <= n; i++)
{
for (int j = m; j >=v[i]; j--)
{
for (int k = 0; k <= s[i] && k * v[i] <= j; k++)
{
dp[j] = max(dp[j], dp[j - v[i] * k] + w[i] * k);
}
}
}
return dp[m];
}
- 二进制优化