一、多重背包问题(单调队列优化)
这里有一个小问题,我还是有点疑问,当你选取的当前物品数量已经超过了当前物品的个数,但是体积还未超过背包容量,是否还会更新dp(下面是Al的回答)
现在,关于您提到的“选取的物品个数超过了当前物品的个数”的情况,实际上在代码中并不会发生。因为我们在更新dp[k]之前,会通过条件检查来确保不会违反物品的数量限制。具体来说,条件if (pp <= tt && k - s * v > q[pp])是用来检查队列头的状态是否对应了一个超过限制的容量(这里“超过限制的容量”实际上是指,如果从这个容量对应的物品数量开始继续考虑当前物品,那么将会违反物品的数量限制)。如果超过了,我们就移动队列头,直到找到一个不超过限制的容量或者队列为空。
我的思考是:当你选取的物品数量已经超过了当前物品的个数,但是体积还未超过背包容量,因为你是一个单调递减的队列,所以每次更新是基于对头更新的,队尾不会对对头造成影响。
#include<iostream>
#include<cstring> // 用于内存拷贝函数memcpy
using namespace std;
// 定义常量M,表示物品的最大数量乘以最大容量,用于数组大小
const int M = 20010;
// n表示物品种类数,m表示背包容量
int n,m;
// pre数组用于存储上一轮动态规划的状态,dp数组用于存储当前轮的状态
int pre[M],dp[M],q[M]; // q数组用于存储单调队列中的元素索引
int main()
{
cin >> n >> m; // 输入物品种类数和背包容量
for(int i = 0;i < n;i ++) // 遍历每种物品
{
memcpy(pre, dp, sizeof dp); // 将当前dp数组的状态复制到pre数组中,为下一轮动态规划做准备
int v,w,s; // v表示物品体积,w表示物品价值,s表示该物品的最大数量
cin >> v >> w >> s;
for(int j = 0;j < v;j ++) // 由于物品数量s和体积v有关,这里通过遍历v的余数来优化状态转移
{
int pp = 0, tt = -1; // pp和tt分别表示单调队列的头和尾
// 遍历所有可能的容量,k表示当前考虑的背包容量
for(int k = j;k <= m;k += v)
{
// 如果队列头已经超出了s*v的限制(即队列头对应的物品数量超过了s),则移动队列头
if(pp <= tt && k - s * v > q[pp])
pp ++;
// 维护单调队列的性质,确保队列中的元素对应的dp值减去相应的成本(即(k - q[pp])/v * w)是递减的
while(pp <= tt && pre[q[tt]] - (q[tt] - q[pp])/v * w <= pre[k] - (k - q[pp])/v * w)
tt --;
// 如果队列不为空,则更新dp[k]为队列头元素对应的dp值加上当前物品的价值
if(pp <= tt)
dp[k] = max(dp[k], pre[q[pp]] + (k - q[pp])/v * w);
// 将当前容量k加入队列尾
q[++ tt] = k;
}
}
}
cout << dp[m]; // 输出背包容量为m时的最大价值
return 0;
}
二、庆功会
这个题就是上面的板子,每什么好说的
#include<iostream>
#include<cstring>
using namespace std;
const int M = 20010;
int n,m;
int pre[M],dp[M],q[M];
int main()
{
cin >> n >> m;
for(int i = 0;i < n;i ++)
{
int v,w,s;
cin >> v >> w >> s;
memcpy(pre, dp, sizeof dp);
for(int j = 0;j < v;j ++)
{
int pp = 0, tt = -1;
for(int k = j;k <= m;k += v)
{
if(pp <= tt && k - s * v > q[pp])
pp ++;
while(pp <= tt && pre[q[tt]] - (q[tt] - q[pp])/v * w <= pre[k] - (k - q[pp])/v * w)
tt --;
if(pp <= tt)
dp[k] = max(dp[k], pre[q[pp]] + (k - q[pp])/v * w);
q[++ tt] = k;
}
}
}
cout << dp[m];
return 0;
}
三、混合背包
这里把01背包当场特殊的完全背包
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1100;
int f[N];
int n,m;
int main()
{
cin >> n >> m;
for(int i = 1;i <= n;i ++)
{
int v,w,s;
cin >> v >> w >> s;
if(!s)
{
for(int j = v;j <= m;j ++)
f[j] = max(f[j], f[j - v] + w);
}
else
{
if(s == -1) s = 1;
for(int k = 1;k <= s;k *= 2)//先枚举物品个数
{
for(int j = m;j >= k * v;j --)
{
f[j] = max(f[j], f[j - k * v] + k * w);
}
s -= k;
}
for(int j = m;j >= s * v;j --)
f[j] = max(f[j], f[j - s * v] + s * w);
}
}
cout << f[m];
return 0;
}