Des
给你一个序列 (a), (0 le a_{i}le2^{k}-1),你可以修改里面的任意元素任意次,修改方法为把序列里的一个数(ai⊕) (2^{k}-1) 其中(⊕)为异或运算,求最多可以构成多少个连续区间([l,r])使得(a_{l}⊕a_{l+1}⊕dotsoplus a_{r-1} ⊕ a_r≠0)
( exttt{Data Range:})
(nle 2 imes 10^5,kle 30).
Sol
最大化异或和不等于 0 这个限制太弱,转成最小化等于 0 的个数就会好做一些。
在每一个位置都可以选择异或上 (2^k-1),那么对于位置 (i),(a_1) 到 (a_i) 的不同异或和的数量是 (2^i) 量级的吗?并不是。由于异或的自反性 (aoplus a=0),所以 (a_1) 到 (a_i) 的异或和只可能有两种不同的值。
令异或前缀和为 (s_i),对于一个右端点 (r),会与前面的 (l) 组成一个异或和为 0 的区间当且仅当 (s_{l-1}oplus s_r=0)。那么如果在最终的答案中某一种相同的前缀和有 (m_i) 种,那么产生的异或和为 0 的区间数量就是 (inom{m_i}2)。
我们需要最小化 (sum_{i=1}^pinom{m_i}2),其中 (p) 为不同的前缀和的数量。
要最小化的是 (sum_{i=1}^pfrac{m_i^2-m_i}2),而 (sum_{i=1}^pm_i=n),是一个定值。根据均值不等式,(m_i) 应当尽量平均,才能使 (sum_{i=1}^pfrac{m_i^2-m_i}2) 尽量小。
那么对于一个位置 (i),设两种前缀和为 (s_{i,1}) 和 (s_{i,2}). 如果 (i) 前面已经有的等于 (s_{i,1}) 的前缀和数量比等于 (s_{i,2}) 的多,就选择 (s_{i,1}) 作为前缀和。反之,选择 (s_{i,2})。这样便能使得 (m_i) 尽量平均。
使用 std::map
即可记录某种前缀和的出现次数。
My code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
int n, add, k, S, a[N];
ll cnt;
map<int, int> m;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
cin >> n >> k;
S = (1 << k);
for(int i = 1; i <= n; i++) cin >> a[i];
m[0] = 1;
for(int i = 1; i <= n; i++) {
int x = add ^ a[i], y = x ^ (S - 1);
if(m[x] > m[y]) swap(x, y);
cnt += m[x]++;
add ^= a[i];
}
cout << 1ll * n * (n + 1) / 2 - cnt << '
';
return 0;
}