「解题报告」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;
}