【UOJ 310】【YBT2023寒假Day8 A】黎明前的巧克力 / 计数题(FWT)
黎明前的巧克力 / 计数题
题目链接:UOJ 310 / YBT2023寒假Day8 A
题目大意
给你一些数,然后两个人分别要选一个互相不交的集合(可以有一遍是空集,但是不能两边都空),然后问你有多少种方案使得两个集合的异或和相同。
思路
首先发现异或值相同那异或起来就是 \(0\)。
那我们可以考虑这么一个东西,就是我们先求它们集合的并集,然后再把集合里面的元素分给它们,那对于大小为 \(x\) 的方案数就是 \(2^x\)。
然后我们考虑怎么弄这个东西,不难想到朴素的 DP。
设 \(f_{i,j}\) 表示前 \(i\) 个元素选一些得到异或和为 \(j\) 的贡献。
然后对于每个数,你可以不选,也可以选(选了就是之前的基础上方案 \(*2\))
然后就是 \(f_{i,j}=f_{i-1,j}+2f_{i-1,j\oplus{a_i}}\)。
然后这个东西显然可以用 FWT 优化,就是每次乘上一个数组 \(f\):在 \(0\) 的位置是 \(1\),在 \(a_i\) 的位置是 \(2\)。
但是这样每次都要乘还是不行啊。
接下来就是神奇的地方,我们发现每次乘上的东西很简单,我们考虑能不能从这里下手,然后我们会发现对于 FWT 之后的每一位,它要么是 \(-1\) 要么是 \(3\)。
(因为 \(0\) 下标肯定是全正贡献,然后 \(a_i\) 的位置可正可负)
那我们如果直接把每个点的 \(f\) 数组加起来会怎样呢?
那众所周知会每个的加起来,那对于每一位,我们得到一个 \(f_i\)。
假设 \(-1\) 有 \(x\) 个 \(3\) 有 \(y\) 个,就有 \(x+y=n,-x+3y=f_i\)。
也就是说我们就可以解方程得到 \(x,y\),然后乘起来得到把这些 \(f\) 乘起来得到的值!
然后 IFWT 回去我们要的答案就是 \(f_0\) 啦。
然后因为不能两个都是空集所以最后答案要减一。
代码
#include<cstdio>
#define ll long long
#define mo 998244353
using namespace std;
const int N = 1e6 + 100;
int n, a[N];
ll f[N << 1];
ll ksm(ll x, ll y) {
ll re = 1;
while (y) {
if (y & 1) re = re * x % mo;
x = x * x % mo; y >>= 1;
}
return re;
}
void FWT(ll *f, ll op, int n) {
for (int mid = 1; mid < n; mid <<= 1)
for (int R = mid << 1, j = 0; j < n; j += R)
for (int k = 0; k < mid; k++) {
int x = f[j | k], y = f[j | mid | k];
f[j | k] = (x + y) * op % mo;
f[j | mid | k] = (x - y) * op % mo;
}
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
f[0]++; f[a[i]] += 2;
}
int m = 1; while (m <= 1000000) m <<= 1;
FWT(f, 1, m);
for (int i = 0; i < m; i++) {
int _1 = 0, _3 = 0;
//_1+_3=n
//-_1+3_3=f[i];
_3 = (f[i] + n) * ksm(4, mo - 2) % mo;
_1 = n - _3;
f[i] = ksm(mo - 1, _1) * ksm(3, _3) % mo;
}
FWT(f, (mo + 1) / 2, m);
printf("%lld", (f[0] - 1 + mo) % mo);
return 0;
}