1774 多重排序(51nod)单调栈解题思路

1、题目说明

  51nod题目地址:https://www.51nod.com/Challenge/Problem.html#problemId=1774
1774 多重排序
  有一个数组a,长度为n,下标从1开始。现在要对a进行m次排序,每一次排序给定两个参数t[i],r[i]表示要对数组的前r[i]个元素进行排序,如果t[i]=1则按照非降序排序,t[i]=2则按照非升序排序。请输出经过m次排序之后的数组a。

输入
  单组测试数据。第一行有两个整数n 和 m (1≤n,m≤200000),表示数组a的长度以及排序次数。第二行有n个整数,给出数组a,(|a[i]|≤10^9)。接下来m行,给出m次排序的信息。第i行有两个整数t[i] 和 r[i] (t[i]∈{1,2} , 1≤r[i]≤n),表示第i次排序是对前面r[i]个元素进行非降序(t[i]=1)或者非升序(t[i]=2)排序。输出输出n个整数,表示经过m次排序之后的数组a。

输入样例
样例输入1
3 1
1 2 3
2 2
样例输入2
4 2
1 2 4 3
2 3
1 2

输出样例
样例输出1
2 1 3
样例输出2
2 4 1 3

2、分析思路:操作优化(单调栈)

  由于n,m可能会达到20万,如果按照基本操作,做m次排序,每次排n个元素,总体的复杂度为O(mnlogn),一定会超时。
  我们随意生成一个样例数组a[8,6,4,7,5,3,1],m的数组格式为[[1,2],[1,4],[2,5]],做3次操作。那么得到的3次数组变化分别为
[6,8,4,7,5,3,1]
[4,6,7,8,5,3,1]
[8,7,6,5,4,3,1]
  我们发现不做第一次和第二次操作,单单做第三次操作和按顺序完成第一、第二、第三次操作的结果一样,对于第三次操作来说第一、第二次操作来说就是无用功。为什么会出现这样的结果,因为第一次操作前2个元素,第二次操作前4个元素,第三次操作前5个元素,无论前两次怎么排序,操作的元素都会被第三次操作的前5个元素所覆盖。所以我们的m次操作,可以只找出后面操作对前面操作有影响的有效操作即可,忽略掉无效操作。在算法实现上来看,这是一个典型的从大到小(栈底到栈顶)的单调栈。
  对于单调栈来说,我们第一次放入单调栈中的一定是整个m中的最大值,第二次放入的是最大值到数组结束位置中的最大值。假设m有8次操作,数组形式为[[1,3],[2,5],[1,4],[1,9],[2,3],[1,8],[2,6],[1,3]]。那么我们首先应该放入最大的值[1,9],即[1,9]可以覆盖前面的所有操作;接下来的[1,8]可以覆盖[2,3]。但由于[1,8]的排序方向1和[1,9]的排序方向相同,所以是无效操作,不用放入单调栈,以此类推,那么最终放入单调栈的结果应该是([1,9],[2,6],[1,3])。
  对于样例,python版本单调栈代码如下:

m=8
opr=[[1,3],[2,5],[1,4],[1,9],[2,3],[1,8],[2,6],[1,3]]
# 单调栈
oprstk=[]
for i in range(m) :
    # 栈顶小于当前元素的都要出栈
    while oprstk and oprstk[-1][1]<=opr[i][1] :
        oprstk.pop()
    # 栈顶跟当前排序方向不一致的,当前元素进栈
    if (not oprstk) or oprstk[-1][0]!=opr[i][0] :
        oprstk.append(opr[i])

这样处理后原来的m次操作,平均变成了logm次操作,当然极端情况下m的操作数量可能没有减少。

3、分析思路:排序优化(数组反转)

  对于每次排序操作,假设用快速排序,平均复杂度O(nlogn),依然很容易就超时。由于上面的操作已经变成了升序、降序、升序、降序这样的间隔,那么对于下一次操作只需要进行局部反转即可。

在这里插入图片描述
  然而,这样子的操作虽然优化了很多,但是还是不能AC所有的测试用例,所以该方法能够优化,但是复杂度仍然太高。

4、分析思路:排序优化(寻找反向构成规律)

  假设对于数据做m次操作的单调栈为[[1,9],[2,7],[1,4],[2,2]],通过演练数组的变化,可以发现每一个步骤可以将结果的尾部的几个数字进行确定,那么可以实现O(n)的结果构造。
  构造的过程中,可以发现从最右往左,先遍历2个(9-7=2)数字9、8;然后从最左往右走遍历3个(7-4=3)数字1、2、3;然后从剩余的数字中继续从最右往最左遍历数字7、6,如此交替遍历。
在这里插入图片描述

5、最终AC的代码

  既然发现了规律,那么编写python代码实现如下:

n,m=map(int,input().split())
arr=list(map(int,input().split()))
opr=[list(map(int,input().split())) for i in range(m)]
# 单调栈
oprstk=[]
for i in range(m) :
    # 栈顶小于当前元素的都要出栈
    while oprstk and oprstk[-1][1]<=opr[i][1] :
        oprstk.pop()
    # 栈顶跟当前排序方向不一致的,当前元素进栈
    if (not oprstk) or oprstk[-1][0]!=opr[i][0] :
        oprstk.append(opr[i])

# oprstk.reverse()
oprstk.append([1,0])
firstsort=oprstk[0]
rightpart=arr[firstsort[1]:]
if firstsort[0]==1 :
    arr=sorted(arr[:firstsort[1]])
else :
    arr=sorted(arr[:firstsort[1]],reverse=True)

revarr=[]
left,right=-1,len(arr)
for i in range(1,len(oprstk)) :
    # 可以确定的排序部分
    k=oprstk[i-1][1]-oprstk[i][1]
    if i%2==1 :
        # 从右往左加
        for j in range(k) :
            right-=1
            revarr.append(arr[right])
    else :
        # 从左往右加
        for j in range(k) :
            left+=1
            revarr.append(arr[left])

revarr.reverse()
res=revarr+rightpart

for i in res :
    print(i,end=' ')
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值