BZOJ 3687

Retired_Doubeecat

2021-05-18 16:51:13

Personal

> [BZOJ3687](https://www.luogu.com.cn/problem/U162587) > > 给定一个可重集,求子集的算数和的异或和。 > > $1 \leq n \leq 1000,\sum a_i \leq 2 \times 10 ^ 6$ <!-- more --> ## 解题思路: 首先这个问题如果没有 $\sum a_i \leq 2 \times 10 ^ 6$ 是一个经典背包,代码大概是 ```cpp f[0] = 1; for (int i = 1;i <= n;++i) { for (int j = sum;j >= a[i];--j) { f[j] ^= f[j - a[i]]; } } ``` 注意这里一个数出现偶数次的话根据异或的性质就等于 $0$ 了,因此我们这里不用保存次数,直接异或即可。 时间复杂度 $O(n\sum a_i)$ 思考一下这个 DP 的本质,这个循环中 ```cpp for (int j = sum;j >= a[i];--j) { f[j] ^= f[j - a[i]]; } ``` 我们的滚动数组实际上干的是这么一回事 ``` a[i] = 2 Flas 0 0 1 0 1 0 Fxor 1 0 1 0 0 0 Fnow 1 0 0 0 1 0 ``` 实际上,可以视为整个数组往左移了两位,那么再注意到我们实际上用到只有 01 两个数,这启发我们使用 `bitset` 优化! 所以,时间复杂度就变成了 $O(\frac{n\sum a_i}{32})$ 可以通过。 ## Code ```cpp const int N = 1e3 + 10; const int M = 2e6 + 10; bitset <M> f; int n,a[N],sum,ans; signed main() { cin >> n; f[0] = 1; for (int i = 1;i <= n;++i) { int x;cin >> x;sum += x; f = f ^(f<<x); } for (int i = 1;i <= sum;++i) { if (f[i]) ans ^= i; } printf("%d\n",ans); return 0; } ```