题目链接
题目大意:
给你一个长度为n的数列,有m次操作,操作有两种:
操作强制在线,n ≤ \le ≤ 5e5,m ≤ \le ≤ 5e5,所有数值域[0, 2 30 2^{30} 230]。
难度:Ag
分析:
线性基可以处理的操作是:
由于线性基的长度很短,因此我们可以将数列所有前缀的线性基保存下来。1到x的线性基可以由1到x-1的线性基通过插入a[x]来求得,这样,我们就可以查询前缀区间的子集异或最大值。现在问题的关键在于,查询区间 [L, R] 时,如何避免 [1, L-1] 的干扰。
考虑线性基的插入过程,如果线性基当前位上已经有值,我们就不能把待插入的值放入这一位,因此线性基上每一位的数,都是对应位上在原数列最左侧的数字。现在我们改变策略,使得线性基上每一位的数,都变成对应位上在原数列最右侧的数字。实现这个策略的方法是:我们额外保存线性基上每一位数在原数列中的位置,插入的时候,如果对应位上的数在原数列中更靠左,就用待插入的数和它交换。基于这种策略,我们在查询区间 [L, R] 时,可以在区间 [1, R] 对应的线性基中查询,对于线性基上每一位的数,如果它在原数组中出现的位置比 L 更靠右,就考虑它对答案的贡献,否则直接跳过这一位。
这个做法的正确性也很显然,通过改变策略,使线性基上每一位数变成对应位上在原数列最右侧的数字,可以看成线性基插入数字的顺序变反,完全不影响线性基的性质。同时,将线性基上所有在原数组中的位置比 x 更靠左的数字删除,可以视为区间 [1, L-1] 的数字还没有被插入线性基。
复杂度:O((n + m) logx),n为初始数列长度,m为操作次数,x为值域大小。
代码:
# include
# define MAXN 1000005
# define MAXM 35
using namespace std;
struct LB
{
int n; //当前已插入的数字个数
int a[MAXN][MAXM]; //保存所有前缀区间的线性基
int b[MAXN][MAXM]; //保存线性基上的数字在原数组上的对应位置
void build()
{
n = 0;
}
void add(int x)
{
int cur = ++n; //表示待插入的数字在原数组上的位置
for (int i = 31; i >= 0; --i)
{
a[n][i] = a[n - 1][i];
b[n][i] = b[n - 1][i];
}
for (int i = 31; i >= 0; --i)
if (x >> i)
{
if (!a[n][i])
{
a[n][i] = x;
b[n][i] = cur;
break;
}
else
{
if (cur > b[n][i]) //如果待插入的数字在原数组上更靠右,则用线性基上的数与其交换
{
swap(a[n][i], x);
swap(b[n][i], cur); //位置值也要交换
}
x ^= a[n][i];
}
}
}
int query(int l, int r)
{
l = l % n + 1;
r = r % n + 1;
if (l > r)
swap(l, r); //处理强制在线
int ret = 0;
for (int i = 31; i >= 0; --i)
if (b[r][i] >= l) //只考虑在原数组中位置比l更靠右的数字的贡献
ret = max(ret, ret ^ a[r][i]);
return ret;
}
};
LB b;
int main()
{
int t;
scanf("%d", &t);
int n, m;
while (t--)
{
b.build();
scanf("%d %d", &n, &m);
int tmp;
for (int i = 0; i < n; ++i)
{
scanf("%d", &tmp);
b.add(tmp);
}
int lastans = 0, opt, x, y; //lastans用于处理强制在线
for (int i = 0; i < m; ++i)
{
scanf("%d %d", &opt, &x);
if (opt == 0)
{
scanf("%d", &y);
printf("%d\n", lastans = b.query(x ^ lastans, y ^ lastans));
}
else
b.add(x ^ lastans);
}
}
return 0;
}
扩展1:
给你一个数列,有两种操作:
原题的弱化版,从原题的解法中,我们应该学到此题的两种解法。
第一种就是,我们保存该数列的所有前缀区间的线性基,这种做法的空间复杂度较高,存在被卡空间的可能。
第二种方法是,我们只保存一个线性基,每次向线性基插入一个数时,额外记录一下插入的数在原数列的位置,查询区间 [1, R] 的时候,对于线性基每一位上的数,如果它在原数列的位置比R更靠左,才考虑它对答案的贡献,否则直接跳过这一位。
扩展2:Codeforces 1100F
给你一个数列,有两种操作(操作可以离线):
跟原题唯一的区别就是操作不强制在线。考虑离线做法,对于每次询问 [L, R] ,把询问按R从小到大排序,然后维护一个线性基。我们每次可以把所有右端点在x之前的询问处理完,再向线性基插入第x个数,所以问题的关键在于,查询区间 [L, R] 时,如何避免 [1, L-1] 的干扰,那么这就回到了原题的解法。
扩展3:洛谷 P4839
给你一排n个桶,每个桶能装任意个数,有两种操作:
这个题并没有什么很好的做法,只能用线段树维护线性基,时间复杂度O(m * logn * log 2 ^2 2X),n为数列长度,m为操作次数,X为值域。