题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6579
题目大意:给一个初始长度为n的序列,有两种操作:
0 l r:询问[l,r]最大子集异或和
1 x:将x加在序列尾部,使得序列边成一个长度为n + 1的新序列
(看到子集异或和就想到了线性基,但数据范围太大,如果用线段树暴力维护区间线性基,复杂度会达到 O ( n ∗ l o g 3 n ) O(n * log^3n) O(n∗log3n),甚至空间也会超出限制,寻思着应该有其它的方法,网上搜到了用 0 1字典树维护并求在[l,r]区间内选一个数出来和某个数异或最大(待补),万能的群友说这题是CF1100F原题,一看果然是,范围是一样大的)
题解:不可能用线段树这类数据结构去维护区间线性基,考虑贪心维护前缀线性基,对于前缀线性基,从高行到低行每一个向量的下标应尽量靠右,在构造线性基时,对于新加入的元素,从高位到低位遍历向量,换掉位置比他靠前的向量,对于被换出来的向量,在与现在的行向量异或后,对剩下还没枚举到的行做相同处理,不断处理下去直到从高行到低行每一行的向量都是尽量靠右的。
求异或最大值时,从高行到低行判断前缀前行基中每一个行向量的下标是否 在 l 的右侧,如果在l的右侧:答案取当前值和异或上该向量后的值中的最大值,如果不在就continue了。
(这种做法和洛谷树状数组专题的HH的项链有点类似,虽然它们都是这一行的向量,但由于新的向量更靠右,对于这个右区间更大的区间来说这个新的行向量贡献更大)
正确性证明:正确性很显然,对于某个区间[l,r]内的元素,都被尽可能大地作为[1,r]前缀线性基的向量。
#include<bits/stdc++.h>
using namespace std;
int t,n,m;
const int maxn = 1e6 + 10;
int f[maxn][30],p[maxn][30],a[maxn];
int main() {
scanf("%d",&t);
while(t--) {
scanf("%d%d",&n,&m);
memset(f,0,sizeof f);
memset(p,0,sizeof p);
for(int i = 1; i <= n; i++)
scanf("%d",&a[i]);
for(int i = 1; i <= n; i++) {
for(int j = 29; j >= 0; j--) {
f[i][j] = f[i - 1][j];
p[i][j] = p[i - 1][j];
}
int tmp = a[i],cp = i;
for(int j = 29; j >= 0; j--) {
if(tmp >> j) {
if(f[i][j]) {
if(p[i][j] < cp) {
swap(p[i][j],cp);
swap(f[i][j],tmp);
}
tmp ^= f[i][j];
}
else {
f[i][j] = tmp;
p[i][j] = cp;
break;
}
}
}
}
int ans = 0;
for(int i = 1; i <= m; i++) {
int type,l,r;
scanf("%d",&type);
if(type == 0) {
scanf("%d%d",&l,&r);
l ^= ans; l = l % n + 1;
r ^= ans; r = r % n + 1;
if(l > r) swap(l,r);
int res = 0;
for(int j = 29; j >= 0; j--)
if(f[r][j] && p[r][j] >= l)
res = max(res,res ^ f[r][j]);
printf("%d\n",ans = res);
}
else {
scanf("%d",&l);
l ^= ans;
a[++n] = l;
int tmp = a[n];
int cp = n;
for(int j = 29; j >= 0; j--)
f[n][j] = f[n - 1][j],p[n][j] = p[n - 1][j];
for(int j = 29; j >= 0; j--) {
if(tmp >> j){
if(f[n][j]) {
if(p[n][j] < cp) {
swap(cp,p[n][j]);
swap(f[n][j],tmp);
}
tmp ^= f[n][j];
}
else {
f[n][j] = tmp;
p[n][j] = cp;
break;
}
}
}
}
}
}
return 0;
}