Loading

「题解」[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;
}
posted @ 2021-06-03 10:46  Aestas16  阅读(82)  评论(1编辑  收藏  举报