1. 程式人生 > 其它 >【題解】CF1054D Changing Array(異或,貪心)

【題解】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\)

\(a_1\)\(a_i\) 的不同異或和的數量是 \(2^i\) 量級的嗎?並不是。由於異或的自反性 \(a\oplus a=0\),所以 \(a_1\)\(a_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;
}