第十二届蓝桥杯省赛第一场C++B组真题(双向排序)

给定序列 (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。

输入样例:

3 3
0 3
1 2
0 2

输出样例:

3 1 2

样例解释

原数列为 (1,2,3)。

第 1 步后为 (3,2,1)。

第 2 步后为 (3,1,2)。

第 3 步后为 (3,1,2)。与第 2 步操作后相同,因为前两个数已经是降序了。

分析:本题思维难度比较大,建议在看博客的同时进行数据模拟以辅助理解

我们先来简单地分析几种特殊的情况,以降序为例,假如我们有两个连续命令,一个是将1~a降序,另一个是将1~b降序,这时候a和b有三种大小关系,若a和b相等,则两个命令是等价的,只需执行一个即可,若a>b,由于一开始已经将1~a降序排列了,那么显然1~b本身就是降序,所以没必要执行后一个命令,那假如a<b呢?这时候我们必须要执行后一个命令了,但是前一个命令的执行又有什么意义呢?因为他已经被后面的命令所包含了,分析这个例子我们不难得出,当多个连续的降序命令需要被执行时,我们只需要执行边界最靠右的那个即可(因为左边界均为1),其余命令都是多余的,同理,简单分析就可以得当多个连续的升序命令需要被执行时,我们只需要执行边界最靠左的那个即可(因为右边界均为n),其他命令也是多余的。这样处理完答案后我们所需要执行的命令就是升序和降序相间排布的,就是一个升序接着一个降序再接着一个升序……,当然,仅仅这样处理后还不行,我们还需要对命令进行其他处理:

我们假设前i个降序命令的右边界是递减的,升序命令左边界是递增的(为了归纳证明有效命令是符合这种规律的),第i个命令是对1~a降序,第i+1个命令是对b~n升序,第i+2个命令是对1~a+x降序

由于前i个降序命令的右边界是递减的,升序命令左边界是递增的,不妨单独考虑区间[b,a],那么我们对[1,a]降序排列时,可以发现,由于之前的降序排列是包含[1,b-1]的,而且之后的升序排列又没有操作到b之前的数,所以b之前的数肯定是要大于区间[b,a]中的数的,那么对区间[1,b]降序就相当于分别对区间[1,b-1]和区间[b,a]降序,但是因为[1,b-1]本来就是降序,所以无需操作,而因为上一次的升序操作肯定是包含[b,a]的,所以我们现在等同于对这个区间进行反转操作。所以我们每次更新到的都只能时两个相邻命令区间的交界部分,而且越来越小,更新方式就是对相交区间进行反转,突然降序命令的右边界出现了一个大于之前的数(升序命令左边界出现了一个小于之前的数也是同样的分析方式),那我们看看可以怎样简化命令,之前的分析能够让我们知道,我们第i个命令和第i+1个命令能够更新到的数只有区间[b,a]上的数(因为前i个降序命令的右边界是递减的,升序命令左边界是递增的,也就是b左边的区间和a右边的区间已经是有序的了),而且前两个命令的操作只更新了区间[b,a],但是这个区间的数被区间[1,a+x]覆盖了,那么之前两个命令进行的更新都会被这次的命令重新打乱,所以说前两个命令就可以直接舍去,直到找到一个降序命令有边界大于a+x为止,那么根据归纳法一步步前推就可以得到我们最终有效命令中降序命令的右边界是递减的,升序命令左边界是递增的

题目分析到这就把命令处理完了,处理答案无非就是每次对相交区间进行反转,那么我们这个可以用双指针来进行操作,这样整体操作就是O(n),但是不可避免的就是可能会出现最后两个命令依旧是存在相交区间的,那么剩余的相交区间里面的顺序就取决于我们最后一次的操作是降序还是升序了。我们可以看作第一次操作是对整个区间进行升序,那么如果操作数是奇数,那么说明最后一次也是升序操作,否则为降序操作,分别进行对应处理即可。

在代码中:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=100010;
typedef pair<int,int> PII;
PII stk[N];
int ans[N],top;
int main()
{
	int n,m;
	cin>>n>>m;
	while(m--)
	{
		int p,q;
		scanf("%d%d",&p,&q);
		if(!p)
		{
			while(top&&stk[top].first==0) q=max(q,stk[top--].second);//求出连续的降序命令中的最大右边界 
			while(top>=2&&stk[top-1].second<=q) top-=2;//若当前右边界大于上一命令的右边界,则上一命令无效 
			stk[++top]={0,q};//把当前命令入栈 
		}
		else if(top)
		{
			while(top&&stk[top].first==1) q=min(q,stk[top--].second);//求出连续的升序命令中的最大左边界
			while(top>=2&&stk[top-1].second>=q) top-=2;//若当前左边界小于上一命令的左边界,则上一命令无效
			stk[++top]={1,q};//把当前命令入栈
		}
	}
	int k=n,l=1,r=n;
	for(int i=1;i<=top;i++)
	{
		if(stk[i].first==0)
			while(r>stk[i].second&&l<=r) ans[r--]=k--;
		else 
			while(l<stk[i].second&&l<=r) ans[l++]=k--;
		if(l>r) break;
	}
	if(top%2)//应该处理升序命令 
		while(l<=r) ans[l++]=k--;
	else//应该处理降序命令
		while(l<=r) ans[r--]=k--;
	for(int i=1;i<=n;i++)
		printf("%d ",ans[i]);
	return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值