单调栈 && 单调队列
单调栈
适用于求左边或者右边离它最近的(比它大或者小)的数
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()] << ' ';
}
}