【題解】CF1054D Changing Array(異或,貪心)
Des
給你一個序列 \(a\), \(0 \le a_{i}\le2^{k}-1\),你可以修改裡面的任意元素任意次,修改方法為把序列裡的一個數\(ai⊕\) \(2^{k}-1\) 其中\(⊕\)為異或運算,求最多可以構成多少個連續區間\([l,r]\)使得\(a_{l}⊕a_{l+1}⊕\dots\oplus a_{r-1} ⊕ a_r≠0\)
\(\texttt{Data Range:}\)
\(n\le 2\times 10^5,k\le 30\).
Sol
最大化異或和不等於 0 這個限制太弱,轉成最小化等於 0 的個數就會好做一些。
在每一個位置都可以選擇異或上 \(2^k-1\),那麼對於位置 \(i\)
令異或字首和為 \(s_i\),對於一個右端點 \(r\),會與前面的 \(l\) 組成一個異或和為 0 的區間當且僅當 \(s_{l-1}\oplus s_r=0\)。那麼如果在最終的答案中某一種相同的字首和有 \(m_i\) 種,那麼產生的異或和為 0 的區間數量就是 \(\binom{m_i}2\)。
我們需要最小化 \(\sum_{i=1}^p\binom{m_i}2\),其中 \(p\)
要最小化的是 \(\sum_{i=1}^p\frac{m_i^2-m_i}2\),而 \(\sum_{i=1}^pm_i=n\),是一個定值。根據均值不等式,\(m_i\) 應當儘量平均,才能使 \(\sum_{i=1}^p\frac{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 << '\n';
return 0;
}