「ROI 2018 Day 2」快速排序

「ROI 2018 Day 2」快速排序

Part1 \(O(n\log n)\)

考虑暴力将每个位置应该有的数移动过来

假设要从\(y\)移动到\(x(x<y)\),那么显然可以选择\([x,y]\)或者\([x,y-1]\)这一段区间进行操作,每次操作之后$x\rightarrow \lfloor \frac{x+y}{2}\rfloor $

因此至多\(\lceil \log (y-x)\rceil\)次操作即可完成一次移动

总的移动上限为\(O(\sum \log i)=O(n\log n)\)

\[\ \]

Part2 没用的优化?

上限确实是\(O(\sum \log i)\),如果人为随机化避免卡满是否有效?

实际当然没有。。

因为期望可以看做是 \(O(\sum\frac{\sum \log {j}}{i})=O(\sum \frac{\log i!}{i})=O(n\log n)\),不影响复杂度级别

\[\ \]

Part3 不一样的\(O(n\log n)\)

考虑逆操作的过程,从末状态开始,每次将区间分裂后交错排放

倒着匹配每一个元素,考虑从\(x\)移动到\(y\)的过程

如果选择区间\([1,y]\),那么\(x\)一步操作即可移动到\(2x\)的位置,也就是一次可以倍增

借一下官方题解里的表格,下表的数据为从\(x\) 移动到\(8\)的步数:

减半操作:

position 1 2 3 4 5 6 7 8
steps 3 3 3 3 2 2 1 0

倍增操作:

position 1 2 3 4 5 6 7 8
steps 3 2 2 1 1 1 1 0

看起来,两种做法的上限都是\(O(\sum \log i)\),即\(O(n\log n)\)

\[\ \]

Part4 有用的优化?

同样的,采取随机化初始状态的方法

这样一来,如果看成是在\([1,y]\)中等概率选取一个数进行移动到\(y\)的操作

对于单个\(y\),复杂度就变成了\(\displaystyle O(\frac{\sum i\frac{y}{2^i}}{y})=O(1)\) (实际上是\(2\)?)

也就是说,总复杂度为\(O(n)\),这一部分会花掉大约\(2n\)次操作

那么只需要能在\(3n\)次操作内完成一个弱智级别的随机骗过去就可以了

Ps:下面这个代码偷懒没有随机

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
char IO;
int rd(){
	int s=0;
	while(!isdigit(IO=getchar()));
	do s=(s<<1)+(s<<3)+(IO^'0');
	while(isdigit(IO=getchar()));
	return s;
}

const int N=3e5+10,INF=1e9+10;

int n,A[N],B[N],I[N];
int AL[N],AR[N],AC;
void S(int l,int r){ 
	AL[++AC]=l,AR[AC]=r;
	rep(i,l,r) B[i]=A[i];
	int p=l;
	for(int i=l+1;i<=r;i+=2) I[A[i]=B[p++]]=i;
	for(int i=l;i<=r;i+=2) I[A[i]=B[p++]]=i;
}
void Move(int x,int y){
	if(x==y) return;
	int t=A[x];
	if(x>=y-x) S(x-(y-x)+1,y);
	else S(1,y);
	Move(I[t],y);
}

int main() {
	rep(i,1,n=rd()) A[I[i]=rd()]=i;
	drep(i,n,1) Move(I[i],i);
	printf("%d\n",AC);
	drep(i,AC,1) printf("%d %d\n",AL[i],AR[i]);
}

posted @ 2021-02-10 22:55  chasedeath  阅读(471)  评论(0编辑  收藏  举报