【YBT2023寒假Day5 A】异或序列(FWT)
异或序列
题目链接:YBT2023寒假Day5 A
题目大意
给你一个自然数序列,你要求出异或和为 0 的最长子序列的长度。
思路
考虑这个最多其实不太好搞,转化一下,把所有数异或起来得到 \(sum\),就是要用最少的数异或出 \(sum\)。
然后找到一个解的方法是线性基,但是用最少的数似乎不太可行。
考虑换一个思路,有关异或的,而且有点类似卷起来的,就是 FWT。
那你考虑把序列里所有出现的数的下标的值都是 \(1\),然后 FWT 之后次方 \(x\) 在 IFWT 就可以判断选恰好 \(x\) 个数能不能。
但是你觉得这样有点不够快,考虑二分一下,那你只要强制 \(f_0=1\) 就可以至少 \(x\) 个数了。
代码
#include<cstdio>
#include<cstring>
#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;
}