双端队列排序--解题报告

本文介绍了如何利用双端队列解决排序问题,通过理解双端队列的特性,结合排序思想,提出O(n)时间复杂度的解题策略。通过对给定样本的分析,揭示了解题的关键在于寻找单调序列的拐点,从而确定所需双端队列的数量。
摘要由CSDN通过智能技术生成

双端队列排序

discription

Sherry现在碰到了一个棘手的问题,有N个整数需要排序。 Sherry手头能用的工具就是若干个双端队列。 她需要依次处理这N个数,对于每个数,Sherry能做以下两件事: 1.新建一个双端队列,并将当前数作为这个队列中的唯一的数; 2.将当前数放入已有的队列的头之前或者尾之后。 对所有的数处理完成之后,Sherry将这些队列排序后就可以得到一个非降的序列。

input

第一行包含一个整数N,表示整数的个数。接下来的N行每行包含一个整数Di,其中Di表示所需处理的整数。

output

其中只包含一行,为Sherry最少需要的双端队列数。

Sample input

6 3 6 0 9 6 3

Sample output

2

100%的数据中N≤200000。

解题思路

1.这道题虽然叫做双端队列,但是如果要使用deque(双端队列)来解的话那么只会gg,重要的是理解deque的特性只能在对头或队尾进行增删。
2.观察这道题的数据规模,发现是两万,如果算法是O(n^2)的话那么就直接gg了,如果是O(n logn),在每一次操作的比较多的话,那么也会超时,所以得用O(n)的算法qwq。
3.如何在只扫描一遍的情况下ac这道题,首先我们要排除掉模拟的方法,因为模拟的话铁定超时,那么怎么办?寻求一个单个双端队列的本质:1.它是一个单调的队列,是升序(非降序);2.所有队列合起来,会得到一个更大的单调队列。这很像一个排序的思想,而将多个有序队列合并成一个更大的有序队列,这不正是大名鼎鼎的合并排序了吗?
4.现在问题已经被简化了,我们只需要求出这次的合并排序中,最少会被分成多少个有序的小组就对了。(注意:小组之间有时是可以合并的,例如[1,2,3],可以和[5,4,3]合并为[1,2,3,3,4,5])
5.如何在常数时间内解决这道题呢?我们可以先得到已经排好序的有序队列,然后再与这个序列进行对比。

最开始的时候数列是 3 6 0 9 6 3
我们最后会得到的序列是 9 6 6 3 3 0
去掉重复的值 9 6 3 0
发现9和6在一起,6和3在一起,3和0在一起
拆分我们现在的序列 [3 6] [0 9] [6 3];
合并 -> [3 6 9] [0 3 6]

神奇的事情发生了,我们居然得到了样例的答案

但是这还没完,时间复杂度,还不够,离答案只剩一点点了!

这样虽然大大简化了难度和时间复杂度(相对于模拟),但是仍不足以支持我们ac,因为得到临近顺序之后再一个个比较的工作量是巨大的,有没有什么办法可以让这个队列的合并从一开始就决定了?亦或是根本就不知道它怎么合并就可以得到答案?蒟蒻的我想不出后者,所以只能绞尽脑汁在前者上面做功夫。
----------
由于排序的是数字的值,我们可以反过来想,我们要干的事就是要在这个值里面找到可以相呼应的区间,那么在排序的时候就得存下它的下标(序号)num,在重新扫描已经排好的序列的时候,就可以直接找扫描序列中下标的‘拐点’,也就是单调性发生改变的点。因为数组下标(num)代表的是这个数在序列中的位置,并且我们扫描的是已经排好的序列,那么在同一个单调的序列中,下标单调性改变的话就必然排不起来那么就需要另一个队列。
题目说道这里已经很清楚了,下面直接上代码~~!

#include<iostream>
#include<cstdio>
#include<algorithm>

using namespace std;

const int maxn=2e5+10;
const int inf = 2e9;
int ans = 1;
int f = 1,noww=inf;
struct node 
{
    int tail, p;
} s[maxn];
 
bool cmp(node x, node y) 
{
    if (x.tail == y.tail) return x.p < y.p;
    return x.tail < y.tail;
}
 
int main()
{
	
    int n;
    cin>>n;
    for (int i = 1; i <= n; i++)
	{
        scanf("%d", &s[i].tail);
        s[i].p = i;
    }
    sort(s + 1, s + n + 1, cmp);
    
    
    for (int i = 1; i <= n; ) 
	{
        int j = i;
        while (s[j + 1].tail == s[j].tail && j < n) j++;
        if (f == 1) 
		{
            if(s[j].p<noww) noww=s[i].p;
            else 
			{
                f=0;
                noww=s[j].p;
            }
            
        } 
		else
		{
            if(s[i].p>noww) noww=s[j].p;
            else
			{
                f=1;
                ans++;
                noww=s[i].p;
            }
        }
        i = j+1;
    }
    cout<<ans;
    return 0;

}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值