\(\texttt{单调栈}\)
\(\texttt{单调栈学习笔记}\)
今天学习的是 数据结构:单调栈。
(n 久之前了……)
\(\texttt{数据结构讲解}\)
\(\texttt{单调栈的类型}\)
-
单调递增栈
-
单调递减栈
- 单调递增栈:指的是一个栈中的元素都是单调递增的。
- 单调递减栈:指的是一个栈中的元素都是单调递减的。
(注:上面说的都是从栈顶到栈底的顺序来单调递增或者是递减的)
\(\texttt{图片实例}\):
- 单调递增栈
- 单调递减栈
\(\texttt{例题讲解}\)
\(\texttt{B3666 (单调栈模板题目)}\)
这道题就是单调栈的模板题目。
题目描述
给定一个数列 \(a\),初始为空。有 \(n\) 次操作,每次在 \(a\) 的末尾添加一个正整数 \(x\)。
每次操作结束后,请你找到当前 \(a\) 所有的后缀最大值的下标(下标从 1 开始)。一个下标 \(i\) 是当前 \(a\) 的后缀最大值下标当且仅当:对于所有的 \(i < j \leq |a|\),都有 \(a_i > a_j\),其中 \(|a|\) 表示当前 \(a\) 的元素个数。
为了避免输出过大,请你每次操作结束后都输出一个整数,表示当前数列所有后缀最大值的下标的按位异或和。
这道题就是标准的单调栈模板题目了,由于是求最大值,所以我们使用单调递增栈,直接模拟即可。
\(\texttt{代码}\)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
const int N = 1e6 + 5;
int n, tot;
ULL st[N], a[N];
int main() {ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);cin >> n;for (int i = 1; i <= n; ++i) cin >> a[i];int ans = 0;for (int i = 1; i <= n; ++i) {while (tot > 0 && a[st[tot]] <= a[i]) tot--, ans ^= st[tot + 1];ans ^= i;st[++tot] = i;cout << ans << '\n';}return 0;
}
\(\texttt{U514895 单调栈-左侧 / 右侧第一个较小值(不重复)}\)
\(\texttt{思路}\)
非常简单,思路上面都讲过了,所以我们可以直接套模板即可,但是右边我们需要注意一点,我的方法是反过来跑一边,但是 c tearcher 说 DP 不能反着跑,所以还可以在每一个元素被 pop 出去时直接将其记录下来,其实都是一样的答案。
左侧
#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
const int N = 1e6 + 5;
int n, a[N], tot, st[N], l[N];
signed main() {ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);cin >> n;for (int i = 1; i <= n; ++i) cin >> a[i];for (int i = 1; i <= n; ++i) {while (tot > 0 && a[st[tot]] >= a[i]) tot--;l[i] = st[tot];st[++tot] = i;}if (n <= 1000) for (int i = 1; i <= n; ++i) cout << (l[i] == 0 ? -1 : l[i]) << ' ';else {int ans = 0;for (int i = 2; i <= n; ++i) ans ^= (l[i] == 0 ? -1 : l[i]);cout << ans << endl;}return 0;
}
右侧
#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
const int N = 1e6 + 5;
int n, a[N], tot, st[N], l[N];
signed main() {ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);cin >> n;for (int i = 1; i <= n; ++i) cin >> a[i];for (int i = n; i >= 1; --i) {while (tot > 0 && a[st[tot]] >= a[i]) tot--;l[i] = st[tot];st[++tot] = i;}if (n <= 1000) for (int i = 1; i <= n; ++i) cout << (l[i] == 0 ? -1 : l[i]) << ' ';else {int ans = 0;for (int i = 2; i <= n; ++i) ans ^= (l[i] == 0 ? -1 : l[i]);cout << ans << endl;}return 0;
}
\(\texttt{U514939 单调栈-左右两侧第一小于等于值(存在重复情况)}\)
\(\texttt{U514895 单调栈-左右两侧第一小于值(存在重复情况)}\)
\(\texttt{思路}\)
其实都是和上面一样的,我们直接就可以把两个代码结合起来,不过需要注意的一点是,这里存在了重复现象,但是也没有关系,我们把上面两个的符号修改即可。
\(\texttt{代码}\)
小于等于
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
const int N = 5e5 + 6;
int n, a[N], l[N], r[N], st[N], tot = 0;
int main() {cin >> n;for (int i = 1; i <= n; ++i) cin >> a[i];for (int i = 1; i <= n; ++i) {while (tot > 0 && a[st[tot]] > a[i]) tot--;l[i] = st[tot];st[++tot] = i;}tot = 0;for (int i = n; i >= 1; --i) {while (tot > 0 && a[st[tot]] > a[i]) tot--;r[i] = st[tot];st[++tot] = i;}for (int i = 1; i <= n; ++i) {cout << (l[i] == 0 ? -1 : l[i]) << ' ' << (r[i] == 0 ? -1 : r[i]) << '\n';}return 0;
}
小于
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
const int N = 5e5 + 6;
int n, a[N], l[N], r[N], st[N], tot = 0;
int main() {cin >> n;for (int i = 1; i <= n; ++i) cin >> a[i];for (int i = 1; i <= n; ++i) {while (tot > 0 && a[st[tot]] >= a[i]) tot--;l[i] = st[tot];st[++tot] = i;}tot = 0;for (int i = n; i >= 1; --i) {while (tot > 0 && a[st[tot]] >= a[i]) tot--;r[i] = st[tot];st[++tot] = i;}for (int i = 1; i <= n; ++i) {cout << (l[i] == 0 ? -1 : l[i]) << ' ' << (r[i] == 0 ? -1 : r[i]) << '\n';}return 0;
}