2022.12.21 总结
今天学的单调栈!!!!!
P5788 【模板】单调栈
题意
求出每个 \(a_i\) 后面第一个大于 \(a_i\) 元素的下标 \(j\),如果没有,则为 0。
思路
首先是暴力,这个很容易,枚举每个数,再把它后面的数都枚举出来,找到就可以了,时间复杂度 \(O(n ^ 2)\)。
所以我们可以考虑枚举答案,用栈来实现:
-
如果栈不为空,且栈顶元素小于当前元素,就说明当前元素是栈顶元素的答案,弹出栈顶,继续比较。
-
否则,压入当前元素。
- 时间复杂度:
因为每个元素都是进栈出栈一次,所以时间复杂度为 \(O(n)\)。(优秀线性!!!!)
- 为什么叫单调栈?
因为栈中元素是单调不增的(推样例就可以看出来了!!!)。
代码
#include <iostream>
using namespace std;
const int N = 3e6 + 10;
int n, a[N], s[N], top, ans[N];
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
while (top && a[s[top]] < a[i]) { // 判断是否为空且比较大小
ans[s[top]] = i; // 记录答案
top--; // 弹出
}
s[++top] = i; // 压入
}
for (int i = 1; i <= n; i++) {
cout << ans[i] << ' ';
}
return 0;
}
B3666 求数列所有后缀最大值的位置
题意
有一个序列 \(a\),初始为空,有 \(n\) 次操作,每次在 \(a\) 的末尾添加一个正整数 \(x\)。
每次操作后,请你求出所有后缀最大值的位置的异或和。
后缀最大值:当 \(i\) 后面的所有元素都小于 \(a_i\) 时,\(a_i\) 就是序列的一个后缀最大值。
思路
单调栈模拟。
-
当栈不为空且栈顶元素小于等于当前元素时,再次异或栈顶元素(两个相同的数异或结果为 0,0 异或任何数都还是等于那个数),弹出。
-
将当前元素压入栈中,并且答案异或当前元素。
栈中元素是单调递增的。
时间复杂度
每个元素进栈出栈一次,\(O(n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, s[N], top;
unsigned long long a[N], ans;
int main() {
ios::sync_with_stdio(0);
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 (top && a[i] >= a[s[top]]) {
ans ^= s[top--]; // 取消栈顶元素
}
ans ^= i, s[++top] = i; // 记录答案并压入
cout << ans << '\n';
}
return 0;
}
P2422 良好的感觉
题意
请你求出 \(\max _ {1 \le i < j \le n} \{\min _ {i \le k \le j} \{a_k\} \times \sum _ {k = i} ^ j {a_k}\}\)。
思路
-
\(O(n ^ 3)\),暴力枚举区间,再循环求出区间和和区间最小值。
-
\(O(n ^ 2)\),暴力枚举区间,枚举区间的同时求出区间最小值和区间和。
-
\(O(n)\),枚举最小值,求出被它所管辖的范围,前缀和实现。
首先,得想清楚这样一件事,如果当前这个数要想成为某个区间的最小值,那么这个区间内就不能有比它更小的数了。
其次,为了让和最大,那么肯定区间中的数越多越好。
所以,思路就很明显了,找出每个数左边和右边的第一个比它要小的数,确定管辖范围,再暴力枚举每一个数,求出最大值。
时间复杂度
每个数进栈出栈一次,\(O(n)\)。
求出最大值,\(O(n)\)。
总时间复杂度为 \(O(n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, a[N], s[N], top, l[N], r[N];
long long sum[N], ans;
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
sum[i] = sum[i - 1] + a[i]; // 前缀和
}
for (int i = 1; i <= n; i++) {
while (top && a[i] <= a[s[top]]) {
r[s[top--]] = i - 1; // 右端点
}
l[i] = s[top] + 1, s[++top] = i; // 左端点
}
for (int i = 1; i <= n; i++) {
ans = max(ans, (sum[r[i]] - sum[l[i] - 1]) * a[i]); // 求答案
}
cout << ans;
return 0;
}