P9291 [ROI 2018] Quick sort - 题解
神仙构造题。
依次考虑每个数
// f[a[i]] = i
for(int i = 1; i <= n; i++) {
while(a[i] != i) {
// i 需放置在子区间的偶数位上
if((f[i] - i) & 1) {
apply(i, f[i]);
} else {
apply(i + 1, f[i]);
}
}
}
但是这种做法不够优秀,对本题而言需要近乎线性的解法。
延续这个思路并没有什么优秀做法,这个时候可以尝试一下转换视角。正难则反,如果这个问题是让我们通过逆操作,将排列 1 2 3 4 5... 操作成给定序列,是否有新的性质呢?
这也引出了本题最人类智慧的一步:运用转置原理,对逆排列通过逆操作进行排序,最后倒序输出操作序列。其中逆操作形如:选择一个子区间,将前半部分与后半部分取出,两部分交替加入结果序列。
这样一来,只要进行一步
这样做好在哪里呢,这时
于是,考虑通过逆操作对原排列的逆排列排序,从
个人认为这里逆操作的动机并不显然,但对这类构造题而言反向考虑的确是一个常见的套路,解构造题还是要善于转换视角。
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
constexpr int N = 3000 + 10;
int n, a[N], b[N], f[N];
inline void maintain() {
for(int i = 1; i <= n; i++) {
f[a[i]] = i;
}
}
vector<pair<int, int>> seq;
inline void apply(int l, int r) {
seq.emplace_back(l, r);
int t = l;
for(int i = l + 1; i <= r; i += 2) {
b[t++] = a[i];
}
for(int i = l; i <= r; i += 2) {
b[t++] = a[i];
}
for(int i = l; i <= r; i++) {
a[i] = b[i];
}
maintain();
}
inline void applyInv(int l, int r) {
seq.emplace_back(l, r);
int mid = (l + r - 1) >> 1, t = 0;
for(int i = l, j = mid + 1; t < r - l + 1; t++) {
if(t & 1) {
b[t] = a[i++];
} else {
b[t] = a[j++];
}
}
for(int i = l; i <= r; i++) {
a[i] = b[i - l];
}
maintain();
}
inline void work(int l, int r) {
while((l << 1) < r) {
applyInv(1, l << 1);
l <<= 1;
}
applyInv(2 * l - r + 1, r);
}
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
int rec[N];
int main() {
// freopen("sort.in", "r", stdin);
// freopen("sort.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> a[i];
}
maintain();
for(int i = 1; i <= n; i++) {
swap(f[i], a[i]);
rec[i] = a[i];
}
maintain();
do {
seq.clear();
for(int i = 1; i <= n; i++) {
a[i] = rec[i];
}
maintain();
int T = 10;
while(T--) {
int l = rng() % n + 1, r = rng() % n + 1;
if(l > r) {
swap(l, r);
}
if(l == r) {
continue;
}
applyInv(l, r);
}
for(int i = n; i >= 1; i--) {
if(f[i] == i) {
continue;
}
work(f[i], i);
}
} while(seq.size() >= 6000);
reverse(seq.begin(), seq.end());
cout << seq.size() << "\n";
for(auto [l, r] : seq) {
cout << l << " " << r << "\n";
}
}