模板及讲解
单调队列
单调队列是什么
单调队列是一个双端队列两边都可以支持删除,并且队列里的数据都是单调的队列。单调队列可以方便查询距离当前位置左边一段连续区间最大最小值,常用于DP优化等单调性问题。类似滑动窗口。
单调队列的实现
以单调递增(维护最小值)的单调队列为例。
1、建立一个队列,都指向数组第一个位置。
2、加入数据$9,11,20$
3、之后加入$10$,序列为$(9,11,20,10)$ ,显然单调性被破坏
4、我们把比$10$大或者等于$10$的数都删除,最后得到的序列是$(9, 10)$,恢复了单调性,最左边元素即为最小值
这就是单调队列的一般步骤了,如果有长度限制,还要在处理前判断左边元素是否超出范围,超出则要删除。
代码
Bzoj 2442, 单调队列优化DP
注意队列数组开两倍,que[l]和l的区别
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define ms(i, j) memset(i, j, sizeof i)
#define LL long long
#define db double
using namespace std;
const int MAXN = 100000 + 5;
int n, k;
LL tot, ei[MAXN], dp[MAXN], que[MAXN * 2];
void clean() {
tot = 0;
ms(dp, 0), ms(que, 0);
}
void solve() {
clean();
for (int i = 1; i <= n; i++) scanf("%lld", &ei[i]), tot += ei[i];
int l = 1, r = 1;
dp[0] = 0;
for (int i = 1; i <= n; i++) {
while (l <= r && que[l] < i - k - 1) l++;
dp[i] = dp[que[l]] + ei[i];
while (l <= r && dp[que[r]] >= dp[i]) r--;
que[++r] = i;
}
LL mn = 1000000000000000000;
for (int i = n - k; i <= n; i++) mn = min(mn, dp[i]);
printf("%lld\n", tot - mn);
}
int main() {
scanf("%d%d", &n, &k), solve();
return 0;
}
单调栈
单调栈是什么
单调栈是一个栈内元素单调的栈。与单调队列相似,但是只有一边删除。
单调栈的实现
以从底到顶单调递增(维护最大值)的单调栈为例。
1、建立一个栈,都指向数组第一个位置。
2、加入数据$9,11,20$
3、之后加入$10$,序列为$(9,11,20,10)$ ,显然单调性被破坏
4、我们把比$10$大或者等于$10$的数都删除,最后得到的序列是$(9, 10)$,恢复了单调性,最上边元素即为最大值
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ms(i, j) memset(i, j, sizeof i)
#define LL long long
using namespace std;
const int MAXN = 80000 + 5;
int n, hi[MAXN], top, stk[MAXN];
void clear() {
top = 0;
}
void init() {
clear();
for (int i=1;i<=n;i++) scanf("%d", &hi[i]);
}
void solve() {
LL ans = 0;
for (int i=1;i<=n;i++) {
if (hi[i]<stk[top]) {
ans += top;
} else {
while (top&&hi[i]>=stk[top]) top--;
ans += top;
}
stk[++top] = hi[i];
}
printf("%lld\n", ans);
}
int main() {
while (scanf("%d", &n)==1&&n) init(), solve();
return 0;
}
常见题型
1、单调队列优化DP
Q:bzoj2442
解:bzoj2442
例题:bzoj 2442
2、单调栈维护i周围比i小的数
Q:bzoj1660
解:bzoj1660
例题:bzoj 1660