单调栈

单调栈

本文代码适用于c++
例题均来自luogu

引入

先看一道例题:https://www.luogu.com.cn/problem/B3666
分析

  1. 显然,第i次操作后,ai一定是后缀最大值。
  2. 不难发现,若ai在第n次操作后(i<n)为后缀最大值的必要条件是ai在第i+1~第n-1次都为后缀最大值。
  3. 更进一步,我们得到一个类似递推的结论:
    若ai在第n-1次操作中是最大后缀值(i<n),且ai>an是ai在第n次操作后是最大后缀值的充要条件。

我们定义一个数组ans:里面的元素是当前状态下a中所有的后缀最大值的下标。

样例数据
5
2 1 3 5 4

n=1时: 由分析1:将1加入到数组ans中;

ans: 1

n=2时: 由分析2:a2<a1,故a1仍在ans中;
由分析1:将2加入到数组ans中;

ans: 1 2

n=3时: 由分析2: a3>a2,故将a2删去;a3>a1,故将a1删去;
由分析1:将3加入ans;

ans: 3

n=4时:由分析2:a4>a3,故将a3删去;
由分析1:将4加入ans;

ans: 4

n=5时: 由分析2:a5<a4,故a4仍然留在ans;
由分析1: 将5加入ans;

ans: 4 5

由题目要求,只需要在每次操作后输出ans中所有元素的异或和即可。

不难发现,在维护数组ans的过程中我们只对最后一个元素进行操作,所以我们可以用栈来实现ans的维护。
代码实现

//注意:使用STL需开O2优化
#include<bits/stdc++.h>
#define int unsigned long long
#define mod 90007
#difine inf 0x7fffffff

using namespace std;
int n;
struct node{
	int x,y;
}a[3000000];//创建数组,x为数字,y为下标
stack<node>s;
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i].x;
		a[i].y=i; //下标为输入时的顺序
	}
	int v=1;
	int ans=0;
	while(v<=n){ //v==n时最后一个元素还没有入栈,所以v=n+1时才能退出
		while(!s.empty()&&s.top().x<a[v].x){
			ans^=s.top().y; //出栈时异或
			s.pop(); //维护栈的单调性,小于她的都出栈
		}
		s.push(a[v]); //入栈
		ans^=a[v].y; //异或新入栈的
		cout<<ans<<endl; //输出答案
		v++; //准备下一个下标
	}
}

举一反三

在上面我们利用维护一个内部元素单调的栈(严格说是栈内元素对应的在a中的数单调)后缀最大值问题。

下面我们再通过两道例题深入理解单调栈的“精神”;

  1. B3667https://www.luogu.com.cn/problem/B3667

分析
本题与上面不同的是:本题要求子区间的后缀最大值。我们容易想到利用双端队列的出入队操作来将问题转化。(或许可以叫单调队列???)
代码实现
(下面用数组实现双端序列 也可直接用STL)

#include<bits/stdc++.h>
#define N 1000009
#define int unsigned long long
using namespace std;
int n, k, x[N], q[N];//q为单调队列 存储最大后缀值下标
signed main()
{
   cin >> n >> k;
   for (int i = 1; i <= n; i++)cin >> x[i];
   int l = 0, r = 0;
   for (int i = 1; i <= n; i++)
   {
   	while (l < r && i - q[l] >= k)l++;//当检查的子列的长度大于k且队列非空时 队首出队
   	while (l < r && x[i] >=x[q[r-1]])r--;//当队尾元素小于待入队元素且队列非空时 队尾出队
   	q[r++] = i;//i入队
   	if (i >= k)cout << r - l << endl;
   }
   return 0;
}
  1. P6510 https://www.luogu.com.cn/problem/P6510

分析
对于区间[l,r],若任意aj>al(r>=j>l),ar=max{ai},al=min{ai};
则从l头奶牛到r头奶牛符合条件。
我们不难发现:al是区间[1,r]的后缀最小值,ar是从右往左看数组a([l,n])的倒数第二个后缀最大值。我们通过单调栈寻找l和r,最后输出r-l+1的最大值即可。
代码实现

#include<bits/stdc++.h>

const int maxn = 100005;

int n, ans, tx, tn;
int a[maxn], sx[maxn], sn[maxn];

int main() {
  scanf("%d", &n);
  for (int i = 1; i <= n; ++i) scanf("%d", a + i);
  for (int i = 1; i <= n; ++i) {
    while (tn && a[sn[tn]] >= a[i]) --tn;
    while (tx && a[sx[tx]] < a[i]) --tx;
    int k = std::upper_bound(sn + 1, sn + 1 + tn, sx[tx]) - sn;
    if (k != (tn + 1)) {
      ans = std::max(ans, i - sn[k] + 1);
    }
    sn[++tn] = i;
    sx[++tx] = i;
  }
  printf("%d\n", ans);
  return 0;
}

posted @ 2025-10-14 23:12  Ahui2667d  阅读(21)  评论(0)    收藏  举报