AcWing 134. 双端队列
双端队列
题意
给出长度为 \(n\) 的序列,从 \(1 \sim n\) 依次处理这 \(n\) 个数字,有两种操作:
- 新建一个双端队列,将当前这个数字放进去。
- 将当前数字放进已有的双端队列中。
问最少需要几个双端队列,可以使得把这若干个双端队列拼接在一起形成非降序序列?
分析
如果要拼接双端队列使得序列有序,那么一定有两种性质:
- 双端队列内的数字是有序的。
- 每个数字在双端队列中一定在值上,相对原序列是连续的。也就是如果有 \(a_1, a_2\) ,那么 \(a_1\) 和 \(a_2\) 中间的数值在原序列不存在,否则这个值无法插进双端队列,一定也是不有序的。
假设最终的序列为 \(A\) ,下标数组为 \(B\)( \(A\) 中每个元素在原序列的下标) ,例如
\(a = [3, 6, 0, 9, 6, 3]\) ,\(b = [1, 2, 3, 4, 5, 6]\) 。
排序后得到
\(A = [0, 3, 3, 6, 6, 9]\) ,那么对应的下标数组 \(B = [3, 1, 6, 2, 5, 4]\) 。
对于下标较小的元素,它是先被插进去的,如果前面的下标比他小,那么可以放进这个双端队列,同理后面的坐标比它大,后入队,可以直接放进去。
那么如果不存在相同的数字,问题就成了找 \(B\) 中的谷地的数量(先递减后递增的子数组)。
如果有相同的数字,这个数字排序的时候是不定的,我们可以任意交换它们的顺序,因为我们要找的是递减后递增的序列,那么这一组相同的数字对应的下标一定要是递增或者递减的,否则结果不会更优。
也就是我们可以把相同数字的下标变成递增的或者递减的。那么我们可以对每一组相同的数字处理(类似缩点)。
Code
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 200010;
PII a[N];
void solve ()
{
int n, ans = 1; cin >> n;
for (int i = 1; i <= n; i ++) cin >> a[i].first, a[i].second = i;
sort(a + 1, a + n + 1);
// dir为-1表示找递减序列,为1表示找递增序列
for (int i = 1, last = n + 1, dir = -1; i <= n; )
{
// 一个区间一个区间地对下标数组进行处理
int j = i;
while(j <= n && a[j].first == a[i].first) ++ j;
-- j; // 找到同一个数字的左右区间[i, j]
int minv = a[i].second, maxv = a[j].second;
if (dir == -1)
{
// 如果为递减序列
if (last > maxv) last = minv; // 调整为递减的
else last = maxv, dir = 1; // 不能调整,找递增
}
else
{
if (last < minv) last = maxv;
else ++ ans, last = minv , dir = -1; // 递增序列找不到了,新开一个dq, 把这一段放进dq里
}
i = j + 1; // 下一组相同的数字
}
cout << ans << endl;
}
signed main ()
{
cout.tie(0)->sync_with_stdio(false);
solve();
return 0;
}