C/C++ 单调栈、单调队列

借助单调性处理问题,及时排除不可能的策略,以保持策略集合的有效性、秩序性。

单调栈

例题:POJ2559 直方图中的最大矩形(在一条直线上有N个宽度为1的矩形,求包含于这些矩形的并集内部的最大的矩形的面积)。
要求时间复杂度:O(N)
解题思路:
若矩形高度从左向右单调递增,则以每个矩形的高度乘以其到右边界的宽度得到一个面积,取这些面积中的最大值。
但若在下一个位置加入一个高度低于“右边界”的矩形,那么该矩形构成的最大面积矩形的高度不会大于其自身的高度,即该矩形前面比该矩形高的矩形高度对之后的矩形的最优解无影响(最优解不在其中),因此排除这些矩形,仅(使用栈)维护一个高度始终单调递增的矩形序列。

题解代码如下

// POJ2559
# include <cstdio>
# include <algorithm>
using namespace std;
# define ll long long


const int MAXN = 1e5;

struct { int h, x;} s[MAXN+1];  // h记录矩形高度,x记录矩形横坐标
int top; ll ans;

int main() {
	int N; s[0].h = s[0].x = 0;
	while (scanf("%d", &N), N) {  // 时间复杂度:O(N)
		top = ans = 0;  // 清空栈,初始化
		for (int i = 1; i <= N; i ++) {
			int h; scanf("%d", &h);
			if (h >= s[top].h && h) s[++top].h = h, s[top].x = i;  // 直接入栈
			else {
				while (h < s[top].h) {  // 取出栈顶直至栈为空,或栈顶矩形高度比当前矩形小
					ans = max(ans, (ll)(i-1-s[top-1].x)*s[top].h); top --;
				}
				s[++top].h = h, s[top].x = i;  // 入栈
			}
		}
		// 把栈中剩余的矩阵弹出,更新答案
		while (top) ans = max(ans, (ll)(N-s[top-1].x)*s[top].h), top --;
		printf("%lld\n", ans);
	}
	
	return 0;
} 

为方便理解,附朴素算法如下

// main
while (scanf("%d", &N), N) {  // 时间复杂度:O(N^2)
		long long ans = 0;
		for (int i = 1; i <= N; i ++) scanf("%d", &h[i]);
		// 朴素算法:统计以每个矩形高度为高所能构成的最大矩形面积
		for (int i = 1; i <= N; i ++) {  
			int l1 = 0, l2 = 0;
			for (int j = i-1; j; j --) if (h[j] >= h[i]) l1 ++; else break;
			for (int j = i; j <= N; j ++) if (h[j] >= h[i]) l2 ++; else break;
			ans = max(ans, (long long)h[i]*(l1+l2));
		} 
		printf("%lld\n", ans);
	}

单调队列

例题1:最大子序和(输入一个长度为 n 的整数序列,从中找出一段长度不超过 m 的连续子序列,使得子序列中所有数的和最大)。
要求时间复杂度:O(n)
解题思路:
求解最大子序列和,相当于求解 m a x i − m ⩽ j ⩽ i − 1 ( s [ i ] − s [ j ] ) , 1 ⩽ i ⩽ N \underset{i-m\leqslant j\leqslant i-1}{max} (s[i]-s[j]) ,1\leqslant i\leqslant N imji1max(s[i]s[j]),1iN(s为前缀和序列)。
若固定i,则该问题等价于求解 m i n i − m ⩽ j ⩽ i − 1 s [ j ] \underset{i-m\leqslant j\leqslant i-1}{min} s[j] imji1mins[j]。此时若出现s[j1]>s[j2](j1<j2),则对j2之后的i来说,s[j1]必不会为最优解(因为s[j2]更小且距离s[i]更近)。
因此枚举每一个i时,仅需维护一个单调递增的队列(双端队列,头尾均可进出的队列),队列头部的点即为该i点对应的最优解(保证头部距离i的长度不超过m的前提下)。

题解代码如下

# include <bits/stdc++.h>
using namespace std;


const int INF = ~(1<<31);
const int MAXN = 3e5;

int s[MAXN+1], head, tail;
struct { int val, p;} q[MAXN+1];  // val记录该点的数值,p记录该点的位置

int main() {
	int n, m, a, ans = -INF; scanf("%d %d", &n, &m); 
	s[0] = 0; head = 1, tail = 0;  // 初始化队列为空
	for (int i = 1; i <= n; i ++) {  // 枚举i,并将s[i-1]入队
		scanf("%d", &a); s[i] = s[i-1] + a;
		if (q[head].p < i-m) head ++;  // 队列头部超出长度范围
		while (s[i-1] <= q[tail].val && head <= tail) tail --;  // 维护单调递增的队列
		q[++tail].val = s[i-1], q[tail].p = i-1;  // 入队
		ans = max(ans, s[i] - q[head].val);
	}
	printf("%d\n", ans);
	
	return 0;
} 

例题2:POJ2823 Sliding Window(给定有n个数的序列,求滑动窗口长度为k时,每个滑动窗口中的最小值和最大值。输出第一行为n-k+1个最小值,第二行为n-k+1个最大值)。
要求时间复杂度:O(n)
解题思路:
类似例题1,对于最小值使用一个单调递增队列维护,最大值使用一个单调递减队列维护。现使用C++STL自带的双端队列(# include <deque>)实现。

方法描述
deque<TYPE> q定义双端队列q
TYPE x = q[?]随机访问([0]为队首)
TYPE x = q.front()/q.back()获得队首/尾元素
q.push_front(x)/q.push_back(x)从队首/尾入队
q.pop_front()/q.pop_back()从队首/尾出队
q.empty()队列为空则返回true

题解代码如下

// POJ2823 
# include <cstdio>
# include <algorithm>
# include <deque>
using namespace std;


const int INF = ~(1<<31);
const int MAXN = 1e6;

int mn[MAXN+1], mx[MAXN+1];  // 分别记录每个滑动窗口的最小值、最大值
struct Q { int val, p;};
deque<Q> qa, qd;  // 递增双端队列qa,递减双端队列qd

int main() {
	int n, k, x; scanf("%d %d", &n, &k); int h = min(n, k); Q t;  // h用于处理k>n的情况
	for (int i = 1; i <= n; i ++) {
		scanf("%d", &x); t.val = x, t.p = i;
		// 判断队列头部是否超出滑动窗口范围
		if (!qa.empty() && i-qa.front().p >= k) qa.pop_front(); 
		if (!qd.empty() && i-qd.front().p >= k) qd.pop_front();
		// 维护队列单调性
		while (!qa.empty() && x <= qa.back().val) qa.pop_back(); qa.push_back(t);
		while (!qd.empty() && x >= qd.back().val) qd.pop_back(); qd.push_back(t);
		// 记录当前窗口中的最小值、最大值
		if (i >= h) mn[i-h+1] = qa.front().val, mx[i-h+1] = qd.front().val;
	}
	for (int i = 1; i <= n-h+1; i ++) printf("%d ", mn[i]); printf("\n");
	for (int i = 1; i <= n-h+1; i ++) printf("%d ", mx[i]); printf("\n");
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是关于用C语言编写单调队列单调栈的一些基本思路: 单调队列单调队列是一种能够在O(1)时间内完成以下操作的队列:在队尾添加元素、在队首删除元素、查找队列中的最小(或最大)元素。 在C语言中,可以使用数组模拟队列,并通过双端队列的思想来实现单调队列。具体实现时,我们可以使用一个数组来存储队列中的元素,并使用两个指针front和rear分别指向队首和队尾元素。为了实现单调性,我们需要维护一个递增(或递减)的队列,即队列中的元素按照一定的顺序排列。为了实现这个目标,我们可以在插入元素时,从队尾开始遍历队列,将所有比插入元素小的元素都出队,最后将插入元素入队。这样,我们就可以保证队列中的元素是单调递增的。查找最小元素时,只需要返回队首元素即可。 以下是用C语言实现单调队列的基本代码: ```c #include <stdio.h> #define MAXSIZE 1000 int queue[MAXSIZE]; // 队列 int front = 0, rear = 0; // 队首和队尾指针 // 判断队列是否为空 int isEmpty() { return front == rear; } // 判断队列是否已满 int isFull() { return rear == MAXSIZE; } // 在队尾添加元素 void enqueue(int x) { while (rear > front && queue[rear - 1] > x) { rear--; } queue[rear++] = x; } // 在队首删除元素 void dequeue() { if (!isEmpty()) { front++; } } // 查找队列中的最小元素 int getMin() { if (!isEmpty()) { return queue[front]; } return -1; } int main() { enqueue(3); enqueue(1); enqueue(5); dequeue(); printf("%d\n", getMin()); return 0; } ``` 单调栈单调栈是一种能够在O(1)时间内完成以下操作的栈:在栈顶添加元素、在栈顶删除元素、查找栈中的最小(或最大)元素。 与单调队列类似,我们也可以使用数组模拟栈,并通过栈的特性来实现单调栈。为了实现单调性,我们需要维护一个递增(或递减)的栈,即栈中的元素按照一定的顺序排列。为了实现这个目标,我们在插入元素时,从栈顶开始遍历栈,将所有比插入元素小的元素都出栈,最后将

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值