【离线+单调栈】AtCoder ABC379 F. Buildings 2
题目
https://atcoder.jp/contests/abc379/tasks/abc379_f
题意
第一行输入两个正整数 \(n, q(2 \leq n \leq 2 \times 10^5, 1 \leq q \leq 2 \times 10^5)\);
第二行输入长度为 \(n\) 的数组 \(a_i(1 \leq a_i \leq n, 1 \leq i \leq n)\),且保证 \(a_i\) 不重复出现;
接下来输入 \(q\) 行询问,每行输入两个整数 \(l, r(1 \leq l < r \leq n)\),对于每个询问你需要输出 \(a_l\) 和 \(a_r\) 在 [r+1, n] 范围内都能看到的数目。一个数 \(a_i\) 能被另一个数 \(a_j\) 看见的定义:满足 \(i > j\) 且在 [j+1, i-1] 范围内没有比 \(a_i\) 更大的数,则称 \(a_i\) 可被 \(a_j\) 看见。
题解
不妨先将问题简单化,假设仅有一个询问 \(l, r(1 \leq l < r \leq n)\),此时需要维护的便是 \(a_l\) 和 \(a_r\) 在范围 [r+1, n] 都能看见的数的数量。
这个问题可以再细化为两个子问题:
- 分别维护出 \(a_l\) 和 \(a_r\) 能看见的数
- 维护出二者能看见的数的交集
对于子问题1,容易联想到单调栈,单调栈的典型应用场景便是在一维数组中寻找第一个满足某条件的数。
对于子问题2,易知的一点是范围 [1, r] 的数一定不在交集内,原因是 \(a_r\) 不可能看见,那么只需要思考在范围 [r+1, n] 的数。对于范围 [r+1, n],\(a_r\) 能看到的数,\(a_l\) 不一定全部能看得见,因为在范围 [l+1,r] 可能存在使得单调栈发生变化的情况;但是 \(a_l\) 能看见的数,\(a_r\) 必定可以看见,亦即 \(a_l\) 在范围 [r+1, n] 能看见的数是 \(a_r\) 在范围 [r+1, n] 能看见的数的子集。于是该问题只需要单调栈从右往左(即数组 \(a\) 下标从大到小)维护到下标 \(l\) 为止,随后在单调栈中使用二分查找下标大于 \(r\) 的数的数量即可。
此时再将问题泛化为多个询问,如果按照询问的顺序进行计算,比如 \(l_i < l_{i+1}\),会造成需要回溯的结果,即无法只在数组 \(a\) 上单方向移动。但是如果对于全部的 \(l_i\) 和 \(l_j\) 均满足 \(l_i > l_j(i < j)\),则依旧可以只在数组 \(a\) 上单方向移动。
所以,可以考虑将询问离线化,使得询问的顺序有序。
时间复杂度:\(O(n + 3q + qlogq + nlogn)\)
空间复杂度:\(O(n + q)\)
参考代码
#include<bits/stdc++.h>
using namespace std;
constexpr int N = 2e5 + 7;
int n, q, top = -1;
int a[N], l[N], r[N], x[N], stk[N], ans[N];
int bs(int ri) {
if (top == -1 || stk[0] < ri) return 0;
int left = 0, right = top, middle;
while (left < right) {
middle = (left + right + 1) >> 1;
if (stk[middle] < ri) right = middle - 1;
else left = middle;
}
return left + 1;
}
int main() {
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
cin >> n >> q;
for (int i = 1; i <= n; ++ i) cin >> a[i];
for (int i = 0; i < q; ++ i) {
cin >> l[i] >> r[i];
x[i] = i;
}
sort(x, x + q, [&](int &i, int &j) {
if (l[i] != l[j]) return l[i] < l[j];
return r[i] < r[j];
});
for (int i = n, j = q - 1; i && j >= 0; -- i) {
while (j >= 0 && l[x[j]] == i) {
ans[x[j]] = bs(r[x[j]] + 1);
-- j;
}
while (top != -1 && a[stk[top]] < a[i]) -- top;
stk[++ top] = i;
}
for (int i = 0; i < q; ++ i) cout << ans[i] << '\n';
return 0;
}
浙公网安备 33010602011771号