ACWING 134 双端队列

https://www.acwing.com/problem/content/136/

题意

Sherry要通过双端队列对N个数进行排序操作。对于每个数 A [ i ] A[i] A[i],只允许进行以下操作。

  1. 新建一个双端队列并将 A [ i ] A[i] A[i]入队
  2. A [ i ] A[i] A[i]从头或尾入队至已有的队列之中。

此外,所有的数处理完成之后,这些队列必须能够被排成非降序列。
求至少需要多少个双端队列

思路

所有的数处理完成之后,这些队列必须能够被排成非降序列。这一要求给实现带来了障碍。因为如果按照读入顺序,未读入的数是不可知的,而题目并不允许出队操作。这也就意味着如果两个数P和Q被放在同一个队列中,而之后又出现了介于P和Q之间的数,那么不论如何操作都不能够满足“所有的数处理完成之后,这些队列必须能够被排成非降序列”这一要求(因不论如何拼接队列都会出现逆序情况)。

这种情况 启发我们不能按照读入顺序,而应该按照数值大小顺序进行计算。

虽然题目描述的是一种以队列实现排序的方式,但我们反向考虑,考虑对于排序好的N个数至少需多少个双端队列与之对应。


注意到题意中,允许将 A [ i ] A[i] A[i]从队头或队尾入队。那么我们不妨思考,这一操作对应着怎样的性质呢?

我们考虑这样一个样例 3 , 6 , 0 , 9 , 6 , 3 3,6,0,9,6,3 3,6,0,9,6,3,下标分别是 1 , 2 , 3 , 4 , 5 , 6 1,2,3,4,5,6 1,2,3,4,5,6
将其升序排序我们得到 0 , 3 , 3 , 6 , 6 , 9 0,3,3,6,6,9 0,3,3,6,6,9,下标分别是 3 , 1 , 6 , 2 , 5 , 4 3,1,6,2,5,4 3,1,6,2,5,4
观察到,对于排序后的下标数组中每个“单谷”,即先减后增的子段,都可以对应一个单独的双端队列。

我们仍然以刚才的样例进行说明。
3 , 1 , 6 , 2 , 5 , 4 3,1,6,2,5,4 3,1,6,2,5,4 不难看出 3 , 1 , 6 3,1,6 3,1,6 就是这样一个“单谷”序列。
那么首先我们把谷底,即下标1对应的元素(3)入队
[ 3 ] [3] [3]
接下来,我们把递减子段下标3对应的元素(0)头插入
[ 0 , 3 ] [0,3] [0,3]
最后我们把递增子段下标6对应的元素(3)尾插入
[ 0 , 3 , 3 ] [0,3,3] [0,3,3]
我们可以看到,队列中的元素已经被排序了。


那么,为什么是“单谷”性质呢?
其实,这就是说原数组中存在“逆序”的部分。
我们仍然以刚才的 3 , 1 , 6 3,1,6 3,1,6为例。此时,我们知道
A [ 3 ] ≤ A [ 1 ] ≤ A [ 6 ] A[3]\leq A[1] \leq A[6] A[3]A[1]A[6]

0 ≤ 3 ≤ 3 0 \leq 3 \leq 3 033
可以看出,原数组中下标 1 , 3 , 6 1,3,6 1,3,6对应的元素 3 , 0 , 3 3,0,3 3,0,3是逆序的,因此我们才需要通过双端队列的头插入和尾插入,将其调整到正确的位置。
如果不满足“单谷”条件,则对应以下几种情况

  1. 排序序列的下标递增,即升序情况。
  2. 排序序列的下标递减,即降序情况。

有同学可能会考虑,是否有“单峰”的情况呢?然而事实上,将“单谷”的情况处理完之后,“单峰”的情况也就不复存在了。

另外,如果存在相同的元素,那么我们可以任意交换它们的顺序以达成更小的段数。

#include <iostream>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 200020;
PII a[N];

int main()
{
    int n;
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        cin >> a[i].first;
        a[i].second = i;
    }
    sort(a, a + n); // pair是双关键字排序
    int res = 1;    // 初始时有一个双端队列
    for (int i = 0, last = 2e9, direction = -1; i < n;)
    {
        int j = i;
        while (j < n && a[i].first == a[j].first)
            j++;
        int maxx = a[j - 1].second;
        int minx = a[i].second;
        if (direction == -1)
        {
            if (last > maxx)
                last = minx; // 当前下降序列,可被插入双端队列尾部
            else
            {
                last = maxx;
                direction = 1;
            }
        }
        else
        {
            if(last>=minx){
                res++;
                direction=-1;
                last=minx;
            }else{
                last=maxx;
            }
        }
        i=j;
    }
    cout<<res<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值