【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;
}
posted @ 2022-05-30 22:57  あおいSakura  阅读(31)  评论(0编辑  收藏  举报