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=' ')