双向排序--蓝桥杯

在这里插入图片描述
首先,测试数据很大,对每一个测试数据都sort一遍只能拿一点样例分,所以我们得想办法优化为nlogn。

寻找特性:
1.样例可简化为一段连续的0/1操作。
比如输入:0 6,0 8,0 7,1 3 ,1 4
可简化为0 8,1 3.
因为对于连续的0操作/1操作,里面有重复的步骤,比如我们先排0 6,再排0 8,会发现结果与只排0 8的结果一样,说明0 6操作是没有必要的,可以删除,所以0 6,0 8,0 7等价于只进行0 8。同理有1 3,1 4操作等价于1 3.
简而言之,对于连续的0或1操作,我们只需要保留区间长度最大的那个操作即可。

2.第一个操作必然是0操作
因为序列本身是升序的,第一步进行1操作没有什么意义。

3.在某个0/1操作中,整个序列的两端部分数字会被固定住,即不会参与后续的排序。
比如:
【0,9】:987654321
【1,2】:9 12345678
【0,7】:9 65432178
【1,4】:965 123478
【0,6】:965 321478
可发现被划掉的数字是不会变的。
因为我们的0或1操作是在前面的0/1操作的基础上进行排序的,即它们公共的部分不用排。比如【1,4】,对下标4到9排序,发现之前的7,8不用移动。且这种性质会一直存在,原因如下:
对于之后的0/1操作,我们不难看出,只有“中间的公共区域”在一直变化(在此我们不妨定义其为“乱序区”,如【0,7】和【1,4】的乱序区为1234),我们更改一下上面的样例:
【0,9】:987654321
【1,2】:9 12345678
【0,6】:9 54321678
【1,4】:954 123678
【0,7】:9 65432178
这个样例等价于下面这个:
【0,9】:987654321
【1,2】:9 12345678
【0,7】:9 65432178
会发现中间的【0,6】和【1,4】变成没必要的操作而被我们删去了。因为【0,7】这里的下标7囊括了【0,6】和【1,4】的乱序区,中间的操作变得没必要了。这说明了什么?说明了只要当前的0或1操作囊括了之前的0/1操作的乱序区,这个0/1操作就可被删去。
我们记录下保留的0/1操作,可发现乱序区是递减的(因为如果一个小的乱序区后面有大的乱序区的话,它会被替代)。

所以我们可以总结一下优化后的0/1操作特点是:
1.先0操作再1操作,接着又是0操作,以此类推
2.两端的数字不会改变,只有中间的区域会改变,且中间的区域会逐渐往中间靠拢变小。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<int,int> PII;
PII s[100005];
int n,m;
int top;
int ans[100005];
int main() {
	cin>>n>>m;
	int x,y;
	for(int i=1; i<=m; ++i) {
		scanf("%d%d",&x,&y);
		if(!x)//0操作 
		{
			if(top&&s[top].first==0) {//对于连续的0操作只保留一个 
				y=max(y,s[top--].second);
			}
			while(top>=2&&s[top-1].second<=y) {//0操作覆盖掉乱序区 
				top-=2;
			}
			s[++top]= make_pair(0,y);//插入 
		}else if(top)//栈不为空,且为1操作。
		{
			//因为是0/1是交替出现的,且只会有一个,所以这里取最小值即可。 
			if(top&&s[top].first==1) {
				y=min(y,s[top--].second);
			}
			while(top>=2&&s[top-1].second>=y) {
				top-=2;
			}
			s[++top]= make_pair(1,y);
		}
	}
	int k=n,l=1,r=n;
	for(int i=1; i<=top; ++i) {//将固定的数字插入ans数组里 
		if(s[i].first==0) {
			while(r>s[i].second&&l<=r) ans[r--]=k--;
		} else {
			while(l<s[i].second&&l<=r) ans[l++]=k--;
		}
		if(l>r) break;
	}
	
	//如果有数字没被固定,就还需要将它们存入ans数组里 
	if(top%2!=0) {
		while(k>0) ans[l++]=k--;
	} else {
		while(k>0) ans[r--]=k--;
	}
	for(int i=1;i<=n;++i)
	{
		printf("%d ",ans[i]);
	}
	return 0;
}

解释一下代码块:

if(top%2!=0) {
		while(k>0) ans[l++]=k--;
	} else {
		while(k>0) ans[r--]=k--;
	}

比如:
【0,9】:987654321
【1,2】:9 12345678
【0,7】:9 65432178
【1,4】:965 123478
【0,6】:965 321478
之前的循环结束后,会发现中间的321并没有存入ans数组里。这里是用来防止这种情况发生的。

注意:l与r指针是用来指向固定好的数字的,从两端向中间靠拢,所以当它们相遇后代表所有数字都排好序了,就结束循环。

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值