单调队列 整理 HDU6319、HDU3530

单调队列

单调队列

一个队列内部的元素具有单调性的一种数据结构,分为单调递增队列和单调递减队列。

  • 注意这里的队列指的是双端队列,两端都可以进行入队和出队操作

性质

  1. 满足从队头到队尾的单调性
  2. 排在队列前面的比排在队列后面的要先进队

实现

令队列长度为 m m m

  • 对于前m个数,只需将其加入到单调递增队列中,入队过程与单调递增栈相同。
  • 对于之后的元素,在入队的同时,还需要将不在当前区间内的元素出队。
  • 记录元素下标,判断队首元素的下标是否 < i − m + 1 < i-m+1 <im+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的单调递增队列
对于每个数实现过程

  1. 2 {2} 2
  2. 2 , 5 {2,5} 2,5
  3. 2 , 4 {2, 4} 2,4
  4. 4 , 5 {4,5} 4,5
  5. 3 {3} 3
  6. 3 , 4 {3,4} 3,4
  7. 3 , 4 , 6 {3,4,6} 3,4,6
  8. 4 , 6 , 8 {4,6,8} 4,6,8
  9. 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 
#include 
#include 
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 
#include 
#include 
#include 
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);
	}
}

你可能感兴趣的:(数据结构)