「解题报告」UOJ310 黎明前的巧克力

我还是太不懂 FWT 了!

首先发现,两个人的集合异或和相等,那么这两个人的集合的并的异或和等于 \(0\),而相对应地,每一个大小为 \(k\) 的异或和为 \(0\) 的集合都有 \(2^k\) 种方案。那么我们实际上就是要找所有异或和等于 \(0\) 的方案数。

考虑集合幂级数刻画,那么我们要求的就是 \(n\) 个集合幂级数的异或卷积,即 \(\prod (1 + 2x^{\{a_i\}})\)

显然不能直接求。观察单个式子的 FWT 结果,发现每个位置的值只有 \(\{-1, 3\}\) 两种情况。原因是因为 \(1\) 会使得每个数加 \(1\)\(2\) 会使得每个数 \(\pm 2\)。但是具体考虑每个位置究竟是加 \(3\) 还是减 \(3\) 仍然不好考虑。然后我就不会做了。

这时候有一个神奇的做法:我们可以直接求出来所有幂级数的 FWT 的和,然后由于我们知道每个位置的值只有 \(\{-1, 3\}\) 两种情况,所以我们是可以求出来有多少个 \(-1\) 与多少个 \(3\) 的。所以直接把所有幂级数加起来,然后一起 FWT,然后求出来分别有多少,快速幂得到点值然后再 IFWT 回去就能得到答案了。

为什么幂级数和的 FWT 等于幂级数 FWT 的和:FWT 是一个线性变换,所以显然。

一开始我想的是类似于 [省选联考 2022] 卡牌 的做法求出每个点值有多少个 \(3\),但是异或 FWT 的式子并不是很优美,完全无法直接求,不像那个题可以直接求前缀和。那么是不是那个题也可以这么做啊。

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1048580, P = 998244353;
const int INV2 = (P + 1) / 2;
int qpow(int a, int b) {
    int ans = 1;
    while (b) {
        if (b & 1) ans = 1ll * ans * a % P;
        a = 1ll * a * a % P;
        b >>= 1;
    }
    return ans;
}
int n, a[MAXN];
int b[MAXN], c[MAXN];
void fwt(int limit, int a[], bool rev) {
    for (int mid = 1; mid < limit; mid <<= 1) {
        for (int l = 0; l < limit; l += (mid << 1)) {
            for (int i = 0; i < mid; i++) {
                int x = a[l + i], y = a[l + i + mid];
                a[l + i] = (x + y) % P, a[l + i + mid] = (x - y + P) % P;
                if (rev) {
                    a[l + i] = 1ll * a[l + i] * INV2 % P;
                    a[l + i + mid] = 1ll * a[l + i + mid] * INV2 % P;
                }
            }
        }
    }
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        b[0]++;
        b[a[i]] += 2;
    }
    int limit = 1 << 20;
    fwt(limit, b, false);
    for (int i = 0; i < limit; i++) {
        int k = ((b[i] + n) % P) / 4;
        c[i] = qpow(3, k);
        if ((n - k) & 1) c[i] = (P - c[i]) % P;
    }
    fwt(limit, c, true);
    printf("%d\n", (c[0] - 1 + P) % P);
    return 0;
}
posted @ 2023-04-21 18:03  APJifengc  阅读(39)  评论(0编辑  收藏  举报