12届蓝桥杯省赛c++b组 I题 双向排序
Posted thejohn2020
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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还需要人为的补上。
好啦,至此这道题就讲完啦!
继续加油:)
以上是关于12届蓝桥杯省赛c++b组 I题 双向排序的主要内容,如果未能解决你的问题,请参考以下文章
蓝桥杯赛前冲刺-枚举暴力和排序专题2(包含历年蓝桥杯真题和AC代码)
2021第十二届蓝桥杯省赛JAVA B组 题目+答案(复现赛)