AcWing 134. 双端队列

双端队列

题意

给出长度为 \(n\) 的序列,从 \(1 \sim n\) 依次处理这 \(n\) 个数字,有两种操作:

  1. 新建一个双端队列,将当前这个数字放进去。
  2. 将当前数字放进已有的双端队列中。

问最少需要几个双端队列,可以使得把这若干个双端队列拼接在一起形成非降序序列?

分析

如果要拼接双端队列使得序列有序,那么一定有两种性质:

  1. 双端队列内的数字是有序的。
  2. 每个数字在双端队列中一定在值上,相对原序列是连续的。也就是如果有 \(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;
}
posted @ 2021-11-25 15:51  Horb7  阅读(57)  评论(0编辑  收藏  举报