前言
众所周知,一个序列的线性基可以用于求解这个序列的子序列异或和最大值。
那么,如果要高效率的求解一个序列的子区间的子序列异或和最大值,就需要用到前缀线性基。
问题
给出一个序列
a
1
.
.
a
n
a_1..a_n
a1..an, 长度为
n
n
n, 以及
q
q
q 个询问, 每个询问要求在
a
L
,
.
.
,
a
R
a_L,..,a_R
aL,..,aR 内取任意的元素,使得异或和最大,求最大的异或值。
问题链接:https://codeforces.com/contest/1100/problem/F
问题本质是什么?
我们需要对于
a
L
.
.
a
R
a_L..a_R
aL..aR, 构造出一个线性基,以求得哪些数位(2进制,下同)可以取1,哪些数位只能取 0。
如果对于每个询问,暴力地去构造线性基,那么最坏复杂度为
O
(
n
×
q
)
O(n\times q)
O(n×q)
有没有效率更高的方法呢?
预处理前缀线性基
其实由于线性基只有30位,所以对于每一个位置
i
∈
[
1
,
n
]
i \in [1,n]
i∈[1,n], 我们都可以预处理前缀线性基。
p
[
i
,
b
]
p[i,b]
p[i,b] 表示对于前
i
i
i 个数,第
b
b
b 位的线性基。
i
d
x
[
i
,
b
]
idx[i,b]
idx[i,b] 表示能构成
p
[
i
,
b
]
p[i,b]
p[i,b] 这个线性基的元素的下标最大值。
那么对于一个询问
L
,
R
L,R
L,R。
也可以采取线性基求子序列异或和最大值相同的方法。
如果对于
i
d
x
[
R
,
b
]
idx[R,b]
idx[R,b] , 大于等于
L
L
L, 说明
p
[
R
,
b
]
p[R,b]
p[R,b]可以加入 答案。
一些有关前缀线性基插入的细节操作
处理完前
i
−
1
i - 1
i−1 个元素的前缀线性基,插入第
i
i
i 个元素时:
我们需要知道更新哪些
p
[
i
,
b
]
i
d
x
[
i
,
b
]
(
0
≤
b
≤
30
)
p[i,b]~~idx[i,b](0 \le b \le 30)
p[i,b] idx[i,b](0≤b≤30) ,哪些是从
p
[
i
−
1
,
b
]
i
d
x
[
i
−
1
,
b
]
p[i - 1,b]~~idx[i - 1, b]
p[i−1,b] idx[i−1,b] 直接转移。
按照常规线性基的插入操作,从高位往低位考虑:
需要记录两个信息
v
a
l
val
val : 考虑到第
b
b
b 位,
p
[
i
−
1
,
b
p[i - 1, b
p[i−1,b ~
30
]
30]
30] 与
a
i
a_i
ai 构成的待插入线性基。
p
o
s
:
pos:
pos: 构成
v
a
l
val
val 中, 下标最小的元素的下标。
对于每一位
b
b
b 若
p
o
s
≥
i
d
x
[
i
−
1
]
[
b
]
pos \ge idx[i - 1][b]
pos≥idx[i−1][b], 那么就插入当前的
v
a
l
val
val, 即:
p
[
i
,
b
]
=
v
a
l
p[i,b] = val
p[i,b]=val。
否则
p
[
i
,
b
]
=
p
[
i
−
1
,
b
]
i
d
x
[
i
,
b
]
=
i
d
x
p
[
i
−
1
,
b
[
p[i,b] = p[i - 1,b]~~idx[i,b] = idxp[i - 1,b[
p[i,b]=p[i−1,b] idx[i,b]=idxp[i−1,b[
然后需要把
p
[
i
−
1
,
b
]
p[i-1,b]
p[i−1,b] 加入
v
a
l
val
val 中。即
v
a
l
X
O
R
p
[
i
−
1
,
b
[
→
v
a
l
val~~XOR~~p[i-1,b[ \to val
val XOR p[i−1,b[→val
时间复杂度
O
(
n
×
b
i
t
s
i
z
e
(
a
i
)
)
b
i
t
s
i
z
e
表示
2
进制位数
O(n \times bitsize(a_i))~~~bitsize表示2进制位数
O(n×bitsize(ai)) bitsize表示2进制位数
参考代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e5 + 10;
int c[N];
int p[N][32];
int idx[N][32];
int n, m;
void inst(int val, int pos) {
int tmpp = pos;
int fl = 1;
for (int i = 30; i >= 0; i -- ) {
if (((val >> i) & 1) && fl) {
if (tmpp > idx[pos - 1][i]) {
idx[pos][i] = tmpp;
p[pos][i] = val;
tmpp = min(tmpp, idx[pos - 1][i]);
val ^= p[pos - 1][i];
if (p[pos - 1][i] == 0) fl = 0;
} else {
val ^= p[pos - 1][i];
p[pos][i] = p[pos - 1][i];
idx[pos][i] = idx[pos - 1][i];
}
} else {
p[pos][i] = p[pos - 1][i];
idx[pos][i] = idx[pos - 1][i];
}
}
}
int query(int l, int r) {
int res = 0;
for (int i = 30; i >= 0; i -- ) {
//cout << idx[r][i] << endl;
if (idx[r][i] >= l) {
res = max(res, res ^ p[r][i]);
}
}
return res;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;;
for (int i = 1; i <= n; i ++ ) {
int x;
cin >> x;
inst(x, i);
}
cin >> m;
for (int i = 1; i <= m; i ++ ) {
int l, r;
cin >> l >> r;
cout << query(l, r) << '\n';
}
}