LOJ2848「ROI 2018 Day 2」快速排序【构造】
给定长为 \(n\) 的排列,操作至多 \(6000\) 次将 \(a\) 排序,每次操作选择子段 \([l,r]\),将其重排为 \(a_{l+1},a_{l+3},\cdots,a_l,a_{l+2},\cdots\)。
\(n\le 3000\)。
按照排序的套路,我们可以 \(\texttt{for }i:n\rightarrow 1\) 将 \(i\) 复位。
可以发现我们可以操作 \(O(\log n)\) 次将位置 \(p\) 的数移到最右边,总操作次数 \(\approx 35000\),不太优秀。
但是实际上比较优秀的是,将最右边的数移到位置 \(p\),当 \(p>\frac n2\) 时一次即可完成(操作子段 \([2p-n+1,n]\)),只需要 \(\log n-\log p+O(1)\) 步,它在平均情况下是 \(O(1)\) 的。
考虑倒着做这个过程。假设要找到若干个操作 \(g_1,g_2,\cdots,g_k\),使得 \(e=g_k\circ g_{k-1}\circ\cdots\circ g_1\circ p\),那么 \(p^{-1}\circ g_1^{-1}\circ\cdots\circ g_k^{-1}=e\),这就是刚才的问题,直接做就完了。
至于不是随机的怎么办呢,可以先随机跑个 \(50\) 次左右,操作次数就是 \(2n+O(1)\) 的。
#include<bits/stdc++.h>
using namespace std;
const int N = 3003, M = 15000;
int rd(){
int ch = getchar(), x = 0;
for(;ch < '0' || ch > '9';ch = getchar());
for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
return x;
}
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
int n, _[N], a[N], b[N], ans[2][M], cnt;
void work(int l, int r){
ans[0][cnt] = l; ans[1][cnt++] = r;
memcpy(b+l, a+l, r-l+1<<2);
int p = l;
for(int i = l+1;i <= r;i += 2) a[i] = b[p++];
for(int i = l;i <= r;i += 2) a[i] = b[p++];
}
int main(){
n = rd();
for(int i = 1;i <= n;++ i) _[rd()] = i;
do { cnt = 0;
memcpy(a, _, n+1<<2);
for(int i = 0;i < 60;++ i){
int l = rng() % n + 1, r = rng() % n + 1;
if(l > r) swap(l, r);
if(l == r) continue;
work(l, r);
}
for(int i = n;i;-- i) if(a[i] != i){
int p = i;
while(a[p] != i) -- p;
while((p<<1)<=i) work(1, p<<=1);
if(p != i) work(2*p-i+1, i);
}
} while(cnt > 6000);
printf("%d\n", cnt);
for(int i = cnt-1;~i;-- i) printf("%d %d\n", ans[0][i], ans[1][i]);
}