zoukankan      html  css  js  c++  java
  • 「题解」[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;
    }
    
  • 相关阅读:
    设计模式学习——前言和目录
    模板颜色搭配
    win7、xp下Meclipse SVN用户名修改
    JS编码解码
    用Javascript进行HTML转义(分享)
    打印异常信息
    lucene 抛出的异常(分享)
    SQL语句优化(分享)
    Java集群之session共享解决方案
    VUE中返回上一页
  • 原文地址:https://www.cnblogs.com/Aestas16/p/CF1168E.html
Copyright © 2011-2022 走看看