12届蓝桥杯省赛c++b组 I题 双向排序

这次要讲的呢是前几个星期刚比完的蓝桥杯c++b组I题:双向排序。这道题呢我考试的时候是直接sort的,所以肯定是过不了所有的案例的。我们得找一下这道题的规律,用数学的角度来写这道题才是正解。

先上题目

给定序列 (a1,a2,⋅⋅⋅,an)=(1,2,⋅⋅⋅,n),即 ai=i。

小蓝将对这个序列进行 m 次操作,每次可能是将 a1,a2,⋅⋅⋅,aqi 降序排列,或者将 aqi,aqi+1,⋅⋅⋅,an 升序排列。

请求出操作完成后的序列。

输入格式
输入的第一行包含两个整数 n,m,分别表示序列的长度和操作次数。

接下来 m 行描述对序列的操作,其中第 i 行包含两个整数 pi,qi 表示操作类型和参数。当 pi=0 时,表示将 a1,a2,⋅⋅⋅,aqi 降序排列;当 pi=1 时,表示将 aqi,aqi+1,⋅⋅⋅,an 升序排列。

输出格式
输出一行,包含 n 个整数,相邻的整数之间使用一个空格分隔,表示操作完成后的序列。

数据范围
对于 30% 的评测用例,n,m≤1000;
对于 60% 的评测用例,n,m≤5000;
对于所有评测用例,1≤n,m≤10^5,0≤pi≤1,1≤qi≤n。

Sample Input:

3 3 0 3 1 2 0 2

Sample output:

3 1 2

这道题的意思就是有n个数,是从1到n,和m次操作,每次操作输入一个p和q,p是0的时候把1到q降序排,p是1的时候把q到n升序排。

根据数据范围你会发现,n和m最大可以达到10的5次方,如果每次操作我们都排序一下的话,时间复杂度是O(mnlogn),会达到10^11以上,这样做肯定是不行的。

其实我们会发现,每次排序都会浪费掉很多时间来处理一些不必要的东西,或者说有很多操作是重复的。就比如1 2 3 4 5 6 7 8 9,我们先把1到3降序排,变成3 2 1 4 5 6 7 8 9,然后再把1到6降序排,变成6 5 4 3 2 1 7 8 9,其实你会发现前面的操作没有必要,我们在1 2 3 4 5 6 7 8 9的时候把1到6降序排就会直接变成最后的答案。换句话说,我们先将[1,x]降序排,再将[1,y]降序排(y>=x),其实我们可以直接把[1,x]降序排这个操作去掉,这是因为[1,x]只是[1,y]的一部分。从[x,n]升序排也是一样的道理,我们先将[x,n]升序排,再将[y,n]升序排(y<=x),其实我们可以直接把[x,n]升序排这个操作去掉。

这样子我们可以发现每次降序排和升序排我们只取范围最大的那个,范围比它小或者等于它的操作其实都是不必要的,我们可以统统去掉。因此呢,我们最后真正需要的操作是升序排降序排依次交替的。我们可以发现,如果第一次操作是[x,n]升序排,那么我们也可以忽略掉,因为刚开始的时候我们的数据本来就是升序的,我们把部分升序排之后也是不会引起任何变化的。所以第一次操作必定是[0,x]降序排。

这样的话我们就可以找规律啦,例如1 2 3 4 5 6 7 8 9
(注意我说的[1,x]或者[x,n]指的都是下标,并不是数字,下标从1开始)
1)第一次我们将[1,3]降序排,变成:3 2 1 4 5 6 7 8 9
2)第二次我们将[7,9]升序排,还是:3 2 1 4 5 6 7 8 9
3)第三次我们将[1,6]降序排,变成:6 5 4 3 2 1 7 8 9
4)第四次我们将[4,9]升序排,变成:6 5 4 1 2 3 7 8 9
5)第五次我们将[1,5]降序排,变成:6 5 4 2 1 3 7 8 9

相信你们发现了一个规律,就是排序的时候,我们会有一些数字被固定,这里我来证明一下。刚开始我们在[1,n]的时候,我们将[1,x]降序排列,由于最初我们的数据都是升序的,所以∀b∈[x+1,n]>∀a∈[0,x]。那么之后我们对[y,n]升序排列(y<=x) 的话其实[x,n]这部分是不变的,注意看上面的 3)和 4),这是因为[y,x]∈[0,x]<[x+1,n],所以[x+1,n]这部分的任意值是始终大于[y,x]中的任意值。因此我们对[y,n]升序排序的话,其实[x,n]这部分不会挪动位置。换句话说,[x,n]已经被固定下来了。

那么就有同学会问啦,谁能保证每次询问都会和上次询问会有交集呢?不相交的时候会怎么样呢?那我们可以观察一下上面的 1) 和 2),它们没有任何交集,所以不会固定任何点,不过到了3)和 4)的时候,他们就有交集了。并且你会发现,1) 和 2)这两次操作其实也是没有必要的,这是因为[1,6]中的6是大于[1,3]中的3的,还是上面的道理,这是因为[1,x]只是[1,y]的一部分。

那么还有一个神奇的地方,[1,x]是[1,y]的一部分可以去掉就算了,为啥接下来的[z,n]升序排也可以去掉?? 我们可以这样想,后半段本身里面有一部分是升序排的,如果我们 z 在升序排那一部分后面,那这个操作本身就没有意义,比如:3 2 1 4 5 6 7 8 9; [3,9]本身已经升序了,我们让[4,9]再升序排,也没有意义。那如果我们在3的前面升序排,比如[2,9],就会变成3 1 2 4 5 6 7 8 9,之后我们对一个大于等于3的降序排,例如[1,6]就会变成6 5 4 3 2 1 7 8 9,你会发现我们直接对3 1 2 4 5 6 7 8 9 的[1,6]降序排结果也是这样。

这样就可以看代码啦

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 100005;
pair<int,int> s[N];
int ret[N];
int n,m;

int main(){
    cin>>n>>m;
    int top=0;
    int k=n;
    for(int i=0;i<m;i++){
        int p,q;
        cin>>p>>q;
        //0 是 1->q降序排列
        if(!p){
            while(top&&s[top].first==0)
                q=max(q,s[top--].second);
            while(top>=2&&s[top-1].second<=q)
                top-=2;
            s[++top]={0,q};
        }
        else if(top){
            while(top&&s[top].first==1)
                q=min(q,s[top--].second);
            while(top>=2&&s[top-1].second>=q)
                top-=2;
            s[++top]={1,q};
        }
    }
    int l=1,r=n;
    for(int i=1;i<=top;i++){
        if(s[i].first==0)
            while(l<=r&&r>s[i].second)
                ret[r--]=k--;
        else
            while(l<=r&&l<s[i].second)
                ret[l++]=k--;
        if(l>r)
            break;
    }
    if(top%2)
        while(l<=r)
            ret[l++]=k--;
    else
        while(l<=r)
            ret[r--]=k--;
    for(int i=1;i<=n;i++)
        cout<<ret[i]<<" ";
    return 0;
}

我们的s是存储所有有效的操作,是pair类型的,第一项存储p,第二项存储q,如果上一次同样的操作范围比这次小,其实上一次操作可以删掉,对于连续同样的操作,我们只取范围最大的那个。这样子我们s里存储的[0,x]中的x都是递减的,[y,n]中的y都是递增的,就会使得交集越来越短。

这里用 l 和 r 两个指针来记录下次固定元素的位置,k来记录下次固定的元素是什么,还是以这个例子:1 2 3 4 5 6 7 8 9
1)第一次我们将[1,6]降序排,变成:6 5 4 3 2 1 7 8 9,这时其实我们的7 8 9 就固定下来了。
2)第二次我们将[4,9]升序排,变成:6 5 4 1 2 3 7 8 9,这时其实我们的6 5 4 就固定下来了。

我们就这样子依次变大 l 和缩小 r ,当两个指针相遇之后或者所有操作都完了,算法结束。时间复杂度为O(n),不过最后不要忘记了,如果是所有操作都完了但是 l 依旧还是小于等于 r 的话,我们还要给数组最后补上,就比如上面的2)如果进行完了之后我们没有操作了,此时只有7 8 9 和6 5 4 被固定上了,1 2 3还需要人为的补上。

好啦,至此这道题就讲完啦!

继续加油:)

  • 71
    点赞
  • 117
    收藏
    觉得还不错? 一键收藏
  • 32
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值