【YBT2023寒假Day5 A】异或序列(FWT)

异或序列

题目链接:YBT2023寒假Day5 A

题目大意

给你一个自然数序列,你要求出异或和为 0 的最长子序列的长度。

思路

考虑这个最多其实不太好搞,转化一下,把所有数异或起来得到 s u m sum sum,就是要用最少的数异或出 s u m sum sum

然后找到一个解的方法是线性基,但是用最少的数似乎不太可行。
考虑换一个思路,有关异或的,而且有点类似卷起来的,就是 FWT。

那你考虑把序列里所有出现的数的下标的值都是 1 1 1,然后 FWT 之后次方 x x x 在 IFWT 就可以判断选恰好 x x x 个数能不能。
但是你觉得这样有点不够快,考虑二分一下,那你只要强制 f 0 = 1 f_0=1 f0=1 就可以至少 x x x 个数了。

代码

#include
#include
#define ll long long
#define mo 998244353

using namespace std;

const int N = 5e5 + 100;
int n, a[N], sum, p[N];
int f[N << 3], g[N << 3], inv2;

int add(int x, int y) {return x + y >= mo ? x + y - mo : x + y;}
int dec(int x, int y) {return x < y ? x - y + mo : x - y;}
int mul(int x, int y) {return 1ll * x * y % mo;}

int ksm(int x, int y) {
	int re = 1;
	while (y) {
		if (y & 1) re = mul(re, x);
		x = mul(x, x); y >>= 1; 
	}
	return re;
}

void FWT(int *f, int op, int n) {
	for (int mid = 1; mid < n; mid <<= 1)
		for (int j = 0; j < n; j += (mid << 1))
			for (int k = 0; k < mid; k++) {
				int x = f[j | k], y = f[j | mid | k];
				f[j | k] = add(x, y); f[j | mid | k] = dec(x, y);
				if (op == -1) f[j | k] = mul(f[j | k], inv2), f[j | mid | k] = mul(f[j | mid | k], inv2);
			}
}

int main() {
	freopen("xor.in", "r", stdin);
	freopen("xor.out", "w", stdout);
	
	inv2 = ksm(2, mo - 2); 
	
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]), sum ^= a[i];
	
	if (!sum) {printf("%d", n); return 0;}
	
	for (int i = 1; i <= n; i++) {//其实不需要(本来想优化一下上界,不过太麻烦了就没写,注意数量不是异或的次数)
		int now = a[i];
		for (int j = 20; j >= 0; j--)
			if ((now >> j) & 1) {
				if (!p[j]) {
					p[j] = now; break;
				}
				now ^= p[j];
			}
	}
	
	for (int i = 1; i <= n; i++)
		f[a[i]] = 1;
	f[0] = 1;
	int limit = 1; while (limit < N) limit <<= 1;
	FWT(f, 1, limit);
	int L = 1, R = n, re = n;
	while (L <= R) {
		int mid = (L + R) >> 1;
		for (int i = 0; i < limit; i++) g[i] = ksm(f[i], mid);
		FWT(g, -1, limit);
		if (g[sum]) re = mid, R = mid - 1;
			else L = mid + 1;
	}
	printf("%d", n - re);
	
	return 0;
} 

你可能感兴趣的:(#,数学或数论,FWT)