题意
- 有一个序列 a \rm a a,你可以花费 v l x o r v l + 1 x o r . . . x o r v r − 1 x o r v r \rm{v_l \ xor\ v_{l+1}\ xor...xor \ v_{r-1} xor \ v_{r}} vl xor vl+1 xor...xor vr−1xor vr的代价询问 a l x o r a l + 1 x o r . . . x o r a r − 1 x o r a r \rm a_l \ xor\ a_{l+1}\ xor...xor\ a_{r-1}\ xor\ a_r al xor al+1 xor...xor ar−1 xor ar,求确定序列 a \rm a a的最小代价。( n ≤ 1 0 5 \rm n \le 10^5 n≤105)
知道序列 a \rm a a等价于知道了 ∀ i ∈ [ 0 , n ] \rm \forall i\in[0,n] ∀i∈[0,n], s i = a 1 x o r a 2 x o r . . . a i − 1 x o r a i \rm s_i= a_1\ xor\ a_2\ xor...a_{i - 1} \ xor\ a_i si=a1 xor a2 xor...ai−1 xor ai。那么每次询问 [ l , r ] \rm[l,r] [l,r]就相当于知道了 s l − 1 x o r s r \rm s_{l - 1} \ xor \ s_r sl−1 xor sr,我们可以对任意两个 s i \rm s_i si连询问他们两个的代价的边,因为确定 n \rm n n个未知数需要 n \rm n n个方程,那么这个张图的最小生成树的权值和就是确定序列的最小代价(已知 s 0 = 0 ) \rm s_0 = 0) s0=0)。
我们考虑克鲁斯卡尔的过程,每次找到当前权值最小的边,连接两个连通块,因为这里的运算为异或,我们用一个Trie来寻找当前权值最小的边,边的总数是 O ( n 2 ) \rm O( n^2) O(n2)级别的,但实际上我们按照Trie自底向上连边的话,任意两个点所连的边只会在他们的LCA处作为连接LCA左右两个子树之间两个连通块的边,我们按照启发式合并的思想枚举子树大小较小的所有点,在另一边所在的Trie上查询异或最小值,复杂度就是 O ( n log n log 1 0 9 ) \rm O(n \log n\log10^9) O(nlognlog109)。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e7 + 3;
struct Trie {
int ch[N][2], size[N], dis[N], val[N], cnt;
void insert(int x) {
int now = 0;
for (int i = 30; ~i; --i) {
int to = x >> i & 1;
if (!ch[now][to])
ch[now][to] = ++cnt;
now = ch[now][to], ++size[now];
}
}
queue<int> q;
int query(int s, int t, int d) {
if (!size[s] || !size[t])
return 0;
if (size[s] > size[t])
swap(s, t);
int ans = INT_MAX;
for (q.push(s); !q.empty(); q.pop()) {
int u = q.front();
if (dis[u] > d) {
int now = t;
for (int i = d; ~i; --i) {
int to = val[u] >> i & 1;
if (!ch[now][to])
to ^= 1;
now = ch[now][to], val[u] ^= to << i;
}
ans = min(ans, val[u] | (1 << (d + 1)));
} else {
for (int j = 0; j < 2; ++j) {
if (size[ch[u][j]]) {
val[ch[u][j]] = val[u] | (j << (d - dis[u]));
dis[ch[u][j]] = dis[u] + 1, q.push(ch[u][j]);
}
}
}
dis[u] = val[u] = 0;
}
return ans;
}
void dfs(int now, int d) {
static long long ans = 0;
if (size[ch[now][0]] > 1)
dfs(ch[now][0], d - 1);
if (size[ch[now][1]] > 1)
dfs(ch[now][1], d - 1);
ans += query(ch[now][0], ch[now][1], d - 1);
if (!now)
printf("%lld\n", ans);
}
} T;
int n, s[N];
int main() {
freopen("secret.in", "r", stdin);
freopen("secret.out", "w", stdout);
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &s[i]);
s[i] ^= s[i - 1];
T.insert(s[i]);
}
T.insert(0), T.dfs(0, 30);
return 0;
}