单调栈 && 单调队列

单调栈

适用于求左边或者右边离它最近的(比它大或者小)的数

1>【模板】单调栈

题目描述

给出项数为 \(n\) 的整数数列 \(a_{1 \dots n}\)

定义函数 \(f(i)\) 代表数列中第 \(i\) 个元素之后第一个大于 \(a_i\) 的元素的下标,即 \(f(i)=\min_{i<j\leq n, a_j > a_i} \{j\}\)。若不存在,则 \(f(i)=0\)

试求出 \(f(1\dots n)\)

输入格式

第一行一个正整数 \(n\)

第二行 \(n\) 个正整数 \(a_{1\dots n}\)

输出格式

一行 \(n\) 个整数表示 \(f(1), f(2), \dots, f(n)\) 的值。

样例

5
1 4 2 3 5
2 5 4 5 0

提示

对于 \(100\%\) 的数据,\(1 \le n\leq 3\times 10^6\)\(1\leq a_i\leq 10^9\)

思路

设当前数为a 2
a 2 之后第一个比a 2 大的数 $<=> $反转这串数,a 2 之前且离a 2 最近的比a 2 大的数
栈顶元素刚好满足这一要求,如果该栈是单调递减的
由于要输出下标,所以就将下标存入栈中

代码

#include <iostream>
#include <stack>
#include <vector>

using namespace std;
const int N = 3e6 + 10;
stack<int> a;
int b[N];
vector<int> c;

int main()
{
	int n; cin >> n;
	for(int i = 1; i <= n; i++) cin >> b[i];
	for(int i = n; i > 0; i--)  //起到反转的效果
	{
		while(!a.empty() && b[a.top()] <= b[i]) a.pop();  //不符合单调递减的条件就会一直出栈
		c.push_back(a.empty() ? 0 : a.top());  //若栈为空,则没有元素比它大;若非空,则栈顶元素就是第一个大于它的
		a.push(i);
	}
	for(int i = c.size() - 1; i >= 0; i--) cout << c[i] << ' ';
}

2>求数列所有后缀最大值的位置

题目描述

给定一个数列 \(a\),初始为空。有 \(n\) 次操作,每次在 \(a\) 的末尾添加一个正整数 \(x\)

每次操作结束后,请你找到当前 \(a\) 所有的后缀最大值的下标(下标从 1 开始)。一个下标 \(i\) 是当前 \(a\) 的后缀最大值下标当且仅当:对于所有的 \(i < j \leq |a|\),都有 \(a_i > a_j\),其中 \(|a|\) 表示当前 \(a\) 的元素个数。

为了避免输出过大,请你每次操作结束后都输出一个整数,表示当前数列所有后缀最大值的下标的按位异或和

输入格式

第一行是一个整数,表示操作次数 \(n\)
第二行有 \(n\) 个整数,依次表示 \(n\) 次操作所添加的整数 \(x_i\)

输出格式

每次操作后请输出一行一个整数,表示当前数列所有后缀最大值下标的按位异或和。

样例

5
2 1 3 5 4
1
3
3
4
1

提示

对于全部的测试点,保证 \(1 \leq n \leq 10^6\)\(1 \leq x_i \lt 2^{64}\)

思路

后缀最大值 单调递减栈的栈底元素

代码

#include <iostream>
#include <stack>

using namespace std;
typedef unsigned long long llu;
const int N = 1e6 + 10;
stack<llu> a;
llu b[N], count;

int main()
{
	llu n; scanf("%llu", &n);
	for(llu i = 1; i <= n; i++)
	{
		scanf("%llu", &b[i]);
		while(!a.empty() && b[a.top()] <= b[i]) count ^= a.top(), a.pop();
		count ^= i, a.push(i);
		printf("%llu\n", count);
	}
}

单调队列

适用于求区间最值, 队列头就是最值

思考步骤

  • 判断是否需要弹出队尾(直至满足单调性)
  • 插入元素 <若在最后才插入元素,当队列为空的情况可能会随机输出一个数>
  • 判断队头是否要出队(区间长度超过特定值)
  • 只有至少构成了一个窗口,才能输出队头

代码

#include <iostream>
#include <deque>

using namespace std;
const int N = 1e6 + 10;
deque<int> a;  //存数字
int b[N];

int main()
{
    int n, k; cin >> n >> k;
    for(int i = 0; i < n; i++) cin >> b[i];
    
    //min
    for(int i = 0; i < n; i++)
    {
        //判断队尾元素是否要出队
        while(a.size() && a.back() > b[i]) a.pop_back();
        //新元素入队
        a.push_back(b[i]);
        //判断队头元素是否要出队(队伍长度是否大于k)
        if(i - k >= 0 && b[i - k] == a.front()) a.pop_front();
        //构成了一个区间,则输出队头元素,队头就是最值
        if(i + 1 >= k) cout << a.front() << ' ';
    }
    cout << endl;
    a.clear();  //记得清空
    
    //max
    for(int i = 0; i < n; i++)
    {
        while(a.size() && a.back() < b[i]) a.pop_back();
        a.push_back(b[i]);
        if(i - k >= 0 && b[i - k] == a.front()) a.pop_front();
        if(i + 1 >= k) cout << a.front() << ' ';
    }
}
#include <iostream>
#include <deque>

using namespace std;
const int N = 1e6 + 10;
int b[N];
deque<int> a;  //存下标

int main()
{
    int n, k; cin >> n >> k;
    for(int i = 0; i < n; i++) cin >> b[i];
    //min
    for(int i = 0; i < n; i++)
    {
        while(!a.empty() && b[a.back()] > b[i]) a.pop_back();
        a.push_back(i);
        if(i - k >= 0 && b[a.front()] == b[i - k]) a.pop_front();
        if(i + 1 >= k) cout << b[a.front()] << ' ';
    }
    a.clear();
    cout << endl;
    //max
    for(int i = 0; i < n; i++)
    {
        while(!a.empty() && b[a.back()] < b[i]) a.pop_back();
        a.push_back(i);
        if(i - k >= 0 && b[a.front()] == b[i - k]) a.pop_front();
        if(i + 1 >= k) cout << b[a.front()] << ' ';
    }
}
posted @ 2024-11-22 18:59  PeachyGalaxy  阅读(28)  评论(0编辑  收藏  举报