23 LCA模拟赛2T2 异或排列 题解
Fast XORting
题面
给定一个 \(2\) 的整数次幂 \(n\) 以及一个 \(0 \sim n - 1\) 的排列 \(a_1, a_2, \cdots a_n\)。
在一次运算中,你可以进行以下两种操作之一:
- 交换两个相邻元素
- 选择任意整数 \(0 \le x \le n - 1\) ,将每个 \(a_i\) 都替换为 \(a_i \oplus x\)
求将该排列从小到大进行排序的最小运算次数。
\(1 \le n \le 2^{18}\)
题解
首先观察题目操作的性质,不难发现,第一个操作相当于在求逆序对数,第二个操作至多只会进行一次。
朴素的想法是枚举每个 \(x\) ,异或后跑一遍归并排序,时间复杂度 \(O(n^2 \log n)\)。
我们要求逆序数实际上就是要比较两个数的大小关系。那么对于任意的两个数,它们的大小关系只会由从高到低第一个不同的二进制位决定,所以我们可以分开考虑每个二进制位。
对于每个二进制位,我们尝试异或后的逆序数少还是异或前的逆序数少,从而得到一个最优的 \(x\) 使得逆序数最少。
时间复杂度 \(O(n \log^2 n)\)
code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
namespace michaele {
typedef long long ll;
const int N = 262200;
int n, m;
int a[N], b[N], c[N];
ll res;
// 归并排序求逆序对数
void work (int l, int r) {
if (l == r) return;
int mid = (l + r) >> 1;
work (l, mid);
work (mid + 1, r);
{
int i = l, j = mid + 1, k = l;
while (i <= mid && j <= r) {
if (b[j] < b[i]) {
res += mid - i + 1;
c[k ++ ] = b[j ++];
} else {
c[k ++ ] = b[i ++];
}
}
while (i <= mid) c[k ++] = b[i ++];
while (j <= r) c[k ++] = b[j ++];
}
for (int i = l; i <= r; i ++) {
b[i] = c[i];
}
}
void solve () {
cin >> m;
n = __lg(m);
for (int i = 0; i < m; i ++) {
cin >> a[i];
}
res = 0;
memcpy (b, a, sizeof a);
work (0, m - 1);
ll ans = res;
int x = 0;
for (int i = 0; i < n; i ++) {
memcpy (b, a, sizeof a);
for (int j = 0; j < m; j ++) {
b[j] ^= (1 << i);
}
res = 0;
work (0, m - 1);
if (res < ans) x ^= (1 << i);
}
memcpy (b, a, sizeof a);
for (int j = 0; j < m; j ++) {
b[j] ^= x;
}
res = 0;
work (0, m - 1);
ans = min (ans, res + 1);
cout << ans << endl;
}
}
int main () {
ios :: sync_with_stdio (0);
cin.tie (0);
cout.tie (0);
michaele :: solve ();
return 0;
}