单调队列优化多重背包问题
本文着重讲解单调队列优化,不对二进制优化进行讲解。
多重背包问题
有N种物品和一个容量是V的背包。第i种物品最多有 si件,每件体积是 ,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。
输入格式
第一行两个整数,N,V(0<N≤1000, 0<V≤20000),用空格隔开,分别表示物品种数和背包容积。
接下来有 N行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤1000
0<V≤20000
0<vi,wi,si≤20000
提示
本题考查多重背包的单调队列优化方法。
输入样例:
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
朴素版多重背包问题
首先我们知道有n个物品,总体积,以及我们每个物品的体积和个数上限
解题思路
状态表示:dp[i][j]
1.集合:表示只选择前i个物品总体积为j
2.属性:最大值
状态计算
集合划分依据:根据第i个物品选0个、选1个…选s个进行划分
dp[i][j]=max(dp[i][j],dp[i−1][j−kv[i]]+w[i]);
dp[i][j] = max(dp[i][j], dp[i - 1][j - kv[i]] + w[i]);
dp[i][j]=max(dp[i][j],dp[i−1][j−kv[i]]+w[i]);
Code:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e3 + 10, M = 2e4 + 10;
int v[N], w[N], s[N];
int dp[N][M];
int n, m;
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i ++) cin >> v[i] >> w[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 ++){
if(j >= k * v[i]){
dp[i][j] = max(dp[i][j], dp[i - 1][j - k * v[i]] + k * w[i]);
}
}
}
}
cout << dp[n][m] << endl;
return 0;
}
时间复杂度:O(NMW)
显然用朴素做法必定是会tle的,所以我们要想一个更快的算法(一个优化)
算法:单调队列优化 O(NV)
实际上我们并不需要二维的dp数组,适当的调整循环条件,我们可以重复利用dp数组来保存上一轮的信息,如下:
dp[j]=max(dp[j],dp[j−kv[i]]+w[i]);
dp[j] = max(dp[j], dp[j - kv[i]] + w[i]);
dp[j]=max(dp[j],dp[j−kv[i]]+w[i]);
dp[j]表示在容量为j时得到的最大价值,那么,针对每一类物品 i ,我们都更新一下 dp[m] -> dp[0] 的值,最后 dp[m] 就是一个全局最优值
dp[m]=max(dp[m],dp[m−v]+w,dp[m−2v]+2w,...,dp[m−sv]+sw);
dp[m]=max(dp[m], dp[m - v] + w, dp[m - 2v] + 2w, ... , dp[m - sv] + sw);
dp[m]=max(dp[m],dp[m−v]+w,dp[m−2v]+2w,...,dp[m−sv]+sw);接下来, 我们把dp[0]->dp[m]都写成下面这种形式:
| dp[0] | dp[v] | dp[2*v] | dp[3*v] | … | dp[k*v] |
| dp[1] | dp[v + 1] | dp[2*v + 1] | dp[3*v + 1] | … | dp[k*v + 1] |
| dp[2] | dp[v + 2] | dp[2*v + 2] | dp[3*v + 2] | … | dp[k*v + 2] |
| … | … | … | … | … | … |
| dp[j] | dp[v + j] | dp[2*v + j] | dp[3*v + j] | … | dp[k*v + j] |
显而易见,m 一定等于 k*v + j,其中 0 <= j < v;
所以,我们可以把dp数组分成j个类,每一类的值都是在同类转换间得到的。
也就是说dp[k*v+j]只依赖于dp[j]、dp[v + j]、dp[2*v+j]、… 、dp[k*v+j]
因为我们需要的是{ dp[j], dp[v+j], dp[2v+j], dp[3v+j], … , dp[k*v+j] } 中的最大值,
可以通过维护一个单调队列来得到结果。这样的话,问题就变成了 j 个单调队列的问题
所以,我们可以得到
dp[j]=dp[j]dp[j+v]=max(dp[j]+w,dp[j+v])dp[j+2v]=max(dp[j]+2w,dp[j+v]+w,dp[j+2v])dp[j+3v]=max(dp[j]+3w,dp[j+v]+2w,dp[j+2v]+w,dp[j+3v])...
\begin{align}
dp[j] = dp[j]
\\dp[j+v] = max(dp[j] + w, dp[j+v])\\
dp[j+2v] = max(dp[j] + 2w, dp[j+v] + w, dp[j+2v])\\
dp[j+3v] = max(dp[j] + 3w, dp[j+v] + 2w, dp[j+2v] + w, dp[j+3v])\\
...
\end{align}
dp[j]=dp[j]dp[j+v]=max(dp[j]+w,dp[j+v])dp[j+2v]=max(dp[j]+2w,dp[j+v]+w,dp[j+2v])dp[j+3v]=max(dp[j]+3w,dp[j+v]+2w,dp[j+2v]+w,dp[j+3v])...
但是,这个队列中前面的数,每次都会增加一个 w ,所以我们需要做一些转换
dp[j]=dp[j]dp[j+v]=max(dp[j],dp[j+v]−w)+wdp[j+2v]=max(dp[j],dp[j+v]−w,dp[j+2v]−2w)+2wdp[j+3v]=max(dp[j],dp[j+v]−w,dp[j+2v]−2w,dp[j+3v]−3w)+3w...
\begin{align}
dp[j] = dp[j]\\
dp[j+v] = max(dp[j], dp[j+v] - w) + w\\
dp[j+2v] = max(dp[j], dp[j+v] - w, dp[j+2v] - 2w) + 2w\\
dp[j+3v] = max(dp[j], dp[j+v] - w, dp[j+2v] - 2w, dp[j+3v] - 3w) + 3w\\
...
\end{align}
dp[j]=dp[j]dp[j+v]=max(dp[j],dp[j+v]−w)+wdp[j+2v]=max(dp[j],dp[j+v]−w,dp[j+2v]−2w)+2wdp[j+3v]=max(dp[j],dp[j+v]−w,dp[j+2v]−2w,dp[j+3v]−3w)+3w...
这样,每次入队的值是 dp[j+kv] - kw
单调队列(滑动窗口)
- 不细讲
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ ){
if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;
while (hh <= tt && a[q[tt]] >= a[i]) tt -- ;
q[ ++ tt] = i;
if (i >= k - 1) printf("%d ", a[q[hh]]);
}
puts("");
hh = 0, tt = -1;
for (int i = 0; i < n; i ++ ){
if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;
while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;
q[ ++ tt] = i;
if (i >= k - 1) printf("%d ", a[q[hh]]);
}
单调队列问题,最重要的两点
1)维护队列元素的个数,如果不能继续入队,弹出队头元素
2)维护队列的单调性,即:尾值 >= dp[j + k*v] - k*w
本题中,队列中元素的个数应该为 s+1 个,即 0 – s 个物品 i
Code:
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 20010;
int n, m;
int f[N], g[N], q[N];
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i ++ )
{
int v, w, s;
cin >> v >> w >> s;
memcpy(g, f, sizeof f);
for (int j = 0; j < v; j ++ )
{
int hh = 0, tt = -1;
for (int k = j; k <= m; k += v)
{
if (hh <= tt && q[hh] < k - s * v) hh ++ ;
while (hh <= tt && g[q[tt]] - (q[tt] - j) / v * w <= g[k] - (k - j) / v * w) tt -- ;
q[ ++ tt] = k;
f[k] = g[q[hh]] + (k - q[hh]) / v * w;
}
}
}
cout << f[m] << endl;
return 0;
}
2274






