「题解」[CF1168E] Xor Permutations
来一发随机化做法。
首先判断无解,由于 \(a_i = p_i \oplus q_i\) 因此所以 \(a_i\) 的异或和为 \(p_i\) 的异或和异或上 \(q_i\) 的异或和,由于 \(p, q\) 都是 \(0,…,2^k−1\) 的排列,因此 \(p_i\) 的异或和异或上 \(q_i\) 的异或和就等于 \(0\) 即 \(a_i\) 的异或和等于 \(0\)。
也就是说如果 \(a_i\) 的异或和不等于 \(0\) 则无解。
我们可以发现如果能构造出一个排列 \(p\) 使得 \(p_i \oplus a_i\) 两两不同,那么 \(q_i = p_i \oplus a_i\) ,也就是说我们只需要构造出一个排列 \(p\) 使得 \(p_i \oplus a_i\) 两两不同就解决了问题。
于是考虑随机化,每次在 \([0,2^k - 1]\) 中随机一个不处在排列 \(p\) 中的数(记为 \(x\)),然后枚举没有被标记过的 \(a_i\) 判断 \(x \oplus a_i\) 是否与当前 \(q\) 中所有的数都不同,若是,则将 \(x\) 加入 \(p\),\(x \oplus a_i\) 加入 \(q\),并标记 \(a_i\)。若不是则继续枚举。
如果所有的未被标记过的 \(a_i\) 中都没有符合条件的,那就随机选择一个未被标记的 \(a_i\),将与 \(x \oplus a_i\) 冲突的 \(q_i\) 所对应的 \(p_i\) 修改为 \(x\) 、对应的 \(a_i\) 去除标记即可。
重复执行以上操作直至 \([0,2^k-1]\) 中所有的数都加入了 \(p\) 中即可构造出答案。
说起来很简单,实际上代码上的细节还是很多,看上去并不比正解好写,只是比正解好想罢了。
代码如下(用注释标示出了一些细节):
/*
I will never forget it.
*/
// 392699
#include <bits/stdc++.h>
using namespace std;
void fr(int &a, bool f = 0, char ch = getchar()) {
for (a = 0; ch < '0' || ch > '9'; ch = getchar()) ch == '-' ? f = 1 : 0;
for (; ch >= '0' && ch <= '9'; ch = getchar()) a = a * 10 + ch - '0';
a = f ? -a : a;
}
int fr() {
int a;
return fr(a), a;
}
const int N = 12;
int arr[(1 << N) + 10], a[(1 << N) + 10], p[(1 << N) + 10], g[(1 << N) + 10];
// arr 内是不处于排列 p 中的数
// p 即为排列 p
// g 是通过 x ^ a_i 得到 i 的桶,即通过冲突的异或和 x ^ a_i 来得到与之对应的 p_i 和 a_i 的下标 i 起到映射的作用
int tmp[(1 << N) + 10];
// tmp 是临时数组,用来存放不满足条件但未被标记的 a_i 的下标 i
bool visval[(1 << N) + 10], vispos[(1 << N) + 10];
// visval 是用于判断异或和 x ^ a_i 是否冲突的桶
// vispos 是用于判断下标 i 对应的 a_i 是否被标记过的桶
struct OI {
int RP, score;
} CSPS2021, NOIP2021;
int main() {
CSPS2021.RP++, CSPS2021.score++, NOIP2021.RP++, NOIP2021.score++, 392699;
srand(time(0));
int n = fr(), xsum = 0, tot = 1 << n;
for (int i = 0; i < (1 << n); i++) fr(a[i]), xsum ^= a[i], arr[i] = i;
if (xsum) return puts("Fou"), 0; // 判断无解
puts("Shi");
for (int i = 0; i < (1 << n); i++) {
int pos = rand() % tot, arrpos = arr[pos]; // 随机化
if (pos != tot - 1) arr[pos] = arr[tot - 1]; // 将 arr[pos] 从 arr 中剔除
tot--, tmp[0] = 0;
bool flag = 0;
for (int j = 0; j < (1 << n); j++)
if (vispos[j] == 0 && visval[arrpos ^ a[j]] == 0) { // 如果符合条件
flag = visval[arrpos ^ a[j]] = vispos[j] = 1; // 打标记
p[j] = arrpos; // 直接加入答案
g[arrpos ^ a[j]] = j; // 建立映射
break;
} else if (vispos[j] == 0) tmp[++tmp[0]] = j; // 不符合条件加入数组供之后随机化使用
if (flag == 0) { // 如果所有未被标记过的 a_i 均不符合条件
int j = tmp[rand() % tmp[0] + 1]; // 随机取一个
vispos[g[arrpos ^ a[j]]] = 0; // 先将对应的 a_i 的标记去掉
arr[tot++] = p[g[arrpos ^ a[j]]]; // 将对应的 x 加入到不处于排列 p 中的数中,供之后随机化使用
p[j] = arrpos; // 加入答案
vispos[j] = 1; // 打标记
visval[p[j] ^ a[j]] = 1; // 打标记
g[p[j] ^ a[j]] = j; // 建立映射
i--; // 因为 p 中剔除了一个数又加入了一个数,所以本质上 p 中数的数量没有改变,将 i-- 与循环中的 i++ 抵消
}
}
for (int i = 0; i < (1 << n); i++) printf("%d ", p[i] ^ a[i]); // 输出 q_i 即 p_i ^ a_i
puts("");
for (int i = 0; i < (1 << n); i++) printf("%d ", p[i]); // 输出 p_i
puts("");
return 0;
}