来一发随机化做法。
首先判断无解,由于 (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;
}