话不多说,直接上题,先看一道单调队列的经典题,理解一下单调队列
(大佬跳过)
AC代码
#include <iostream>
using namespace std;
const int M = 1000100;
int que[M], head, tail, ans1[M], ans2[M], que2[M];
int map1[M];
int main()
{
int n, k;
cin >> n >> k;
for (int i = 0; i < n; ++i) {
cin >> map1[i];
}
int cnt = 0, cnt2 = 0;
// 为了方便判断队头出框,所以队列中存储map1下标
head = 0;
tail = -1;
for (int i = 0; i < n; ++i) {
if (head <= tail && i - que[head] > k - 1) head++; // 1.防止队列为空 2.弹出队头(队头后移)
while (head <= tail && map1[i] >= map1[que[tail]]) tail--; // 1.防止队列为空 2.因为如果前面的数比要加入的数要小,那么必定不能作为最大值,所以弹出 维护队列单调性
que[++tail] = i; // 1.将map1下标放入队尾 如果之前已经将队删除干净,则新加入的为队头,如果没删除干净则目前最大的还是队头
if (i >= k - 1)
ans1[cnt++] = map1[que[head]];
// 细品
}
head = 0;
tail = -1;
// 类似上边,不过一个队头是最大,一个队头是最小
for (int i = 0; i < n; ++i) {
if (head <= tail && i - que2[head] > k - 1) head++;
while (head <= tail && map1[que2[tail]] >= map1[i]) tail--;
que2[++tail] = i;
if (i >= k - 1)
ans2[cnt2++] = map1[que2[head]];
}
for (int i = 0; i <= n - k; ++i) {
cout << ans2[i] << " ";
}
cout << endl;
for (int i = 0; i <= n - k; ++i) {
cout << ans1[i] << " ";
}
cout << endl;
return 0;
}
背包总容量为V,背包中物品数量为N
每件物品质量或者体积为w,价值为v,数量为s
不用单调队列优化的情况下,对于每一个物品,应该是每次求出
0 <= j <= V
dp[j] = max(dp[j], dp[j - w] + v, dp[j - 2 * w] + 2 * v, … , dp[j - k *w] + k * v)。
对于每个物品我们可以将背包的0 - V分为 w个组
1组: dp[0], dp[0 + w], dp[0 + 2 * w], … , dp[0 + k * w]
2组: dp[1], dp[1 + w], dp[1 + 2 * w], … , dp[1 + k * w]
3组: dp[2], dp[2 + w], dp[2 + 2 * w], … , dp[2 + k * w]
.
.
.
w组: dp[w - 1], dp[w - 1 + w], dp[w - 1 + 2 * w], … , dp[w - 1 + k * w]
对于每组,下标可以以(j + k * w)表示出,不同的组不会互相影响
dp[j] = max(dp[j])
dp[j + w] = max(dp[j + w], dp[j + w - w] + v)
dp[j + 2 * w] = max(dp[j + 2 * w], dp[j + w] + v, dp[j] + 2 * v)
…
dp[j + k * w] = max(dp[j + k * w], dp[j + (k - 1) * w] + v, … , dp[j + k - (k - 1) * w] + (k - 1) * v, dp[j + (k - k) * w] + k * v)
可以转化为
dp[j] = max(dp[j]) = max(dp[j])
dp[j + w] = max(dp[j + w], dp[j + w - w] + v) = max(dp[j + w] - v, dp[j])) + v
dp[j + 2 * w] = max(dp[j + 2 * w], dp[j + w] + v, dp[j] + 2 * v) = max(dp[j + 2 * w] - 2 * v, dp[j + w] - v, dp[j + 2 * w]) + 2 * v
…
dp[j + k * w] = max(dp[j + k * w], dp[j + (k - 1) * w] + v, … , dp[j + (k - (k - 1)) * w] + (k - 1)* v, dp[j + (k - k)] + k * v)
= max(dp[j + k * w] - k * v, dp[j + (k - 1) * w]- (k - 1) * v, …, dp[j + (k - (k - 1)) * w] - (k - (k - 1)) * v, dp[j + (k - k) * w] - (k - k) * v) + k * v
因为每次都需要加上(k * v),(k * v) 是一个常数,所以每次都只需要比较 dp[j + x * w] + x * v 就可以了,本次是dp[j + k * w],之前最小是dp[j + que[tail] * w] + que[tail] * v,利用先删后加,将不可能出现的大小删除除。
对应这一段
while (head <= tail && dp1[j + k * w] - k * v >= dp1[j + que[tail] * w] - que[tail] * v)
tail--;
而在修改值的时候不要忘了加上 k * v
这也是为什么比较大小的时候和修改值的时候不同的原因
例题
题目链接
代码
#include <iostream>
#include <cstring>
using namespace std;
const int M = 20020;
int dp[M], dp1[M], que[M];
int main()
{
int N, V;
cin >> N >> V;
int w, v, s;
for (int i = 0; i < N; ++i) {
cin >> w >> v >> s;
memcpy(dp1, dp, sizeof(dp));
for (int j = 0; j < w; ++j) {
int head = 0, tail = -1;
for (int k = 0; j + k * w <= V; ++k) {
if (head <= tail && k - que[head] > s) head++;
while (head <= tail && dp1[j + k * w] - k * v >= dp1[j + que[tail] * w] - que[tail] * v)
tail--;
que[++tail] = k;
dp[j + k * w] = dp1[j + que[head] * w] + (k - que[head]) * v;
}
}
}
cout << dp[V] << endl;
return 0;
}