单调队列
单调队列
一个队列内部的元素具有单调性的一种数据结构,分为单调递增队列和单调递减队列。
- 注意这里的队列指的是双端队列,两端都可以进行入队和出队操作
性质
- 满足从队头到队尾的单调性
- 排在队列前面的比排在队列后面的要先进队
实现
令队列长度为 m m m
- 对于前m个数,只需将其加入到单调递增队列中,入队过程与单调递增栈相同。
- 对于之后的元素,在入队的同时,还需要将不在当前区间内的元素出队。
- 记录元素下标,判断队首元素的下标是否 < i − m + 1 < i-m+1 <i−m+1
例如:有一个数列
2
,
5
,
4
,
5
,
3
,
4
,
6
,
8
,
5
{2,5,4,5,3,4,6,8,5}
2,5,4,5,3,4,6,8,5,以此构造一个
m
=
3
m=3
m=3的单调递增队列
对于每个数实现过程
- 2 {2} 2
- 2 , 5 {2,5} 2,5
- 2 , 4 {2, 4} 2,4
- 4 , 5 {4,5} 4,5
- 3 {3} 3
- 3 , 4 {3,4} 3,4
- 3 , 4 , 6 {3,4,6} 3,4,6
- 4 , 6 , 8 {4,6,8} 4,6,8
- 5 {5} 5
作用
- 可以实现单调栈寻找第一个比他小或者比他大的数
- 可以实现长度为m区间内的最小数和最大数(单调递减的单调队列的队首最大值,反之亦然)
代码
deque<int> q;
for (int i = 1; i <= n; i++) {
while (!q.empty() && a[q.back()] >= a[i])q.pop_back();
q.push_back(i);
while (!q.empty() && q.front() < i - m + 1)q.pop_front();
ans[i] = a[q.front()];
}
int l = 1, r = 0, q[1010];
for (int i = 1; i <= n; i++) {
while (r >= l && a[q[r]] >= a[i])r--;
q[++r] = i;
while (r >= l && q[l] < i - m + 1)l++;
ans[i] = a[q[l]];
}
HDU6319 Problem A. Ascending Rating
题意
给出n个数
让你查询每一个长度为m的区间内,从第一个元素开始最大值变换了几次,最大值是谁
把他们与i异或求和.
分析
- 当我们维护一个单调递减的单调队列的时候,队列的头即为该区间最大的数
- 变换的值为该区间递增到最高高度的数组长度,即为该区间从后往前的递减的单调队列
- 综上,维护从后往前递减的单调队列即可
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#pragma warning (disable:4996)
const int maxn = 10000005;
typedef long long LL;
class QIO {
public:
char buf[1 << 21], * p1 = buf, * p2 = buf;
int getc() {
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++;
}
int read() {
int ret = 0, f = 0;
char ch = getc();
while (!isdigit(ch)) {
if (ch == '-')
f = 1;
ch = getc();
}
while (isdigit(ch)) {
ret = ret * 10 + ch - 48;
ch = getc();
}
return f ? -ret : ret;
}
}io;
int n, m, k, p, q, r, mod;
int a[maxn];
void Read() {
n = io.read();
m = io.read();
k = io.read();
p = io.read();
q = io.read();
r = io.read();
mod = io.read();
for (int i = 1; i <= k; i++)
a[i] = io.read();
while (++k <= n)
a[k] = (LL(p) * a[k - 1] + LL(q) * k + r) % mod;
}
int maxrating[maxn], Count[maxn];
int dq[maxn];
void Queue(){
int head = 1, tail = 0;
for (int i = n; i > n - m + 1; i--) {
while (tail >= head && a[dq[tail]] <= a[i])tail--;
dq[++tail] = i;
}
for (int i = n - m + 1; i >= 1; i--) {
while (tail >= head && a[dq[tail]] <= a[i])tail--;
dq[++tail] = i;
while (tail >= head && dq[head] > i + m - 1)head++;
maxrating[i] = a[dq[head]] ^ i;
Count[i] = (tail - head + 1) ^ i;
}
}
int main() {
int t; t = io.read();
while (t--) {
Read();
Queue();
LL Rating = 0, Counts = 0;
for (int i = 1; i <= n - m + 1; i++) {
Rating = Rating + maxrating[i];
Counts = Counts + Count[i];
}
printf("%lld %lld\n", Rating, Counts);
}
}
HDU3530 Subsequence
题意
给出一个数组
找出最大区间的最大值与最小值的差值在区间
[
m
,
k
]
[m,k]
[m,k]以内
分析
- 用一个单调递减队列维护最大值,一个单调递增队列维护最小值
- 每一次入队操作后,判断当前最大值与最小值之差是否大于k
- 若是,将两个单调队列中队首下标最小的元素出栈
- 用 p 表示最近的一个出队元素的下标。则 p+1 即为左边界
- 直到当前最大值与最小值之差小于等于k
- 当前区间即为 p+1~k,若最大值与最小值之差大于等于m,更新答案
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn = 100005;
typedef long long LL;
int a[maxn], q[maxn], dq[maxn];
int main() {
int n, m, k;
while (~scanf("%d%d%d", &n, &m, &k)) {
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
int l = 1, r = 0, dl = 1, dr = 0, p = 0, maxl = 0;
for (int i = 1; i <= n; i++) {
while (r >= l && a[q[r]] <= a[i])r--;
q[++r] = i;
while (dr >= dl && a[dq[dr]] >= a[i]) dr--;
dq[++dr] = i;
while (r >= l && dr >= dl && a[q[l]] - a[dq[dl]] > k) {
if (q[l] < dq[dl]) {
p = q[l];
l++;
} else {
p = dq[dl];
dl++;
}
}
if (a[q[l]] - a[dq[dl]] >= m) {
maxl = max(maxl, i - p);
}
}
printf("%d\n", maxl);
}
}