【CFGym102154C】Quick Sort(随机增量法)
- 给定一个 \(1\sim n\) 的排列,你需要将它排序。
- 每次操作可以选择一个长度为偶数的区间 \([l,r]\),将它重排为 \(a_{l+1},a_{l+3},\cdots,a_r,a_l,a_{l+2},\cdots,a_{r-1}\)。
- 要求给出一组不超过 \(15000\) 步的方案。
- \(1\le n\le3000\)
两种暴力
首先是最容易想到的一个做法。假设我们要把 \(x\) 移到 \(i\),就每次选中 \([i,x]\) 或 \([i+1,x]\)(视奇偶性决定),这样一来 \(i,x\) 间的距离每次减半,总复杂度为 \(O(n\log n)\),会被卡掉。
但实际上我们可以反向考虑写出另一种暴力,即通过若干操作把有序排列变成给定排列。发现这个操作反过来就是把给定区间拆成左右两半,然后交叉拼在一起,因此每次可以把区间中间的那个数移到最右边,相当于让中间那个数下标倍增,总复杂度仍然为 \(O(n\log n)\)。
随机增量法
第一种暴力难以优化。而对于上面的第二种暴力,在 \(a\) 随机的情况下,移动 \(i\) 的期望复杂度实际上应该是:
\[\frac1{n-i+1}\sum_{k=1}^{n-i+1}(\log(n-i+1)-\log(n-i+1-k))=O(1)
\]
而要让 \(a\) 随机,只要在一开始先对序列进行 \(2n\) 次随机操作即可。
代码:\(O(n^2)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 3000
#define K 15000
using namespace std;
int n,a[N+5],p[N+5],t1[N+5],t2[N+5],ct,ql[K+5],qr[K+5];
I void Work1(CI l,CI r)//正向操作
{
RI i,c1=0,c2=0;for(ql[++ct]=l,qr[ct]=r,i=l;i<=r;++i) (i^l)&1?t2[++c2]=a[i]:t1[++c1]=a[i];
for(i=1;i<=c1;++i) a[l+i-1]=t2[i];for(i=1;i<=c2;++i) a[l+c1+i-1]=t1[i];
}
I void Work2(CI l,CI r)//反向操作
{
RI i,m=l+r>>1,c1=0,c2=0;for(ql[++ct]=l,qr[ct]=r,i=m;i>=l;--i) t1[++c1]=p[i];for(i=r;i^m;--i) t2[++c2]=p[i];
for(i=l;i<=r;++i) p[i]=(i-l)&1?t1[c1--]:t2[c2--];
}
int main()
{
RI i,x,y;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i);if(n==1) return puts("0"),0;
RI l,r;for(srand(324682339),i=1;i<=2*n;++i) {W(l=rand()%n+1,r=rand()%n+1,l>r&&(swap(l,r),0),!((r-l)&1));Work1(l,r);}//随机操作
for(i=1;i<=n;++i) p[a[i]]=i;for(i=n;i;--i) {for(x=i;p[x]^i;--x);W(x^i) (x<<1)<=i?(Work2(1,x<<1),x<<=1):(Work2(2*x-i+1,i),x=i);}//反向,每次倍增下标
for(printf("%d\n",ct),i=1;i<=2*n;++i) printf("%d %d\n",ql[i],qr[i]);for(i=ct;i>2*n;--i) printf("%d %d\n",ql[i],qr[i]);return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒