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;
}