题目链接VJ
题目链接HDU
(本文是对%%%%%的学习笔记,同时使用了本题作为例题学。)
线性基真是个简单而强大的东西,然而我因为很少遇到线性基的题目基本没有怎么学习,线性基是一个基于贪心的数据处理技巧。
如果定义原序列通过若干个元素异或的到的新集合为序列的异或域,那么线性基就是一个异或域与原序列异或域相同的极小集合。
线性基三大性质:
1.原序列里面的任意一个数都可以由线性基里面的一些数异或得到
2.线性基里面的任意一些数异或起来都不能得到 0 00
3.线性基里面的数的个数唯一,并且在保持性质一的前提下,数的个数是最少的
首先是线性基能够维护的东西:
序列异或
(序列取若干个数异或起来的最值,即异或域的极值 :)
最小值:线性基最小值。
最大值:从高位到低位贪心取,以代码来说为:
if((res^d[i])>res)res^=d[i];
接下来就是线性基进阶技巧:
序列异或第k小:
时间复杂度在于预处理
(
l
o
g
A
i
)
2
(log{A_i})^2
(logAi)2;
预处理就是让线性基的数位性质变化成为:当且仅当当前位取了,当前位的二进制位上才会为1;
void work()//处理线性基
{
for(int i=1;i<=60;i++)
for(int j=1;j<=i;j++)
if(d[i]&(1ll<<(j-1)))d[i]^=d[j-1];
}
ll k_th(ll k)
{
if(k==1&&tot<n)return 0;//特判一下,假如k=1,并且原来的序列可以异或出0,就要返回0,tot表示线性基中的元素个数,n表示序列长度
if(tot<n)k--;//类似上面,去掉0的情况,因为线性基中只能异或出不为0的解
work();
ll ans=0;
for(int i=0;i<=60;i++)
if(d[i]!=0)
{
if(k%2==1)ans^=d[i];
k/=2;
}
}
当序列经过了处理,那么很显然相当于:
d
0
d
1
d
2
d
3
…
…
d
t
d_0d_1d_2d_3……d_t
d0d1d2d3……dt
其中
d
i
d_i
di表示第i位的线性基取/不取。排名k自然也变成了d序列;
如果k =
10
1
(
2
)
101_(2)
101(2)
即:
第一小的线性基元素取了即:
d
0
=
1
d_0=1
d0=1,
第二小的线性基元素不取即:
d
1
=
0
d_1=0
d1=0,
第三小的线性基元素取了即:
d
2
=
1
d_2=1
d2=1。
于是第i位线性基元素贡献为:
(
1
<
<
i
&
k
)
∗
d
[
i
]
(1<<i\&k)*d[i]
(1<<i&k)∗d[i];
动态维护序列线性基(带插入和删除)
因为过于少见,暂且备板,挂起来。
回到这个题:
本题希望维护区间线性基;修改操作只有在最右边放置新的数(仅有两种操作,一种是a[++n] = x,一种是query(l,r))
采用一个技巧:
线性基的性质之一就是只要保持线性基大小不变,数具体是哪些是可以变的。于是使用前缀线性基:
查询l,r,只需要使用
1
,
r
1,r
1,r的线性基减去
1
,
(
l
−
1
)
1,(l-1)
1,(l−1)的影响即可;
那么如何去除1,(l-1)的影响;替换掉线性基里面的数,记录每一个数在原序列中的位置,如果某一位可以使用多种线性基:为了消除先后影响,一律使用最新的,即每一个位的线性基元素都应该是可选元素集合中位置最靠后面的。
换而言之:以d[i][j]表示1-i的线性基第j位;
pos[i][j]表示1-i的线性基第j位用的数本体在原序列中位置;
;
void _insert(int now,int k,int val)//a[loc] = k;
{
for(int i =29;i>=0;--i){p[now][i] =p[now-1][i];loc[now][i]=loc[now-1][i];}
for(int i =29;i>=0;--i)
{
if( !((1<<i)&val) )continue;
if(!p[now][i])
{
p[now][i] = val;
loc[now][i] = k;
break;
}
else if(loc[now][i]<k)
{//当前更新
swap(loc[now][i],k);
swap(p[now][i],val);
}
val^=p[now][i];
}
}
插入函数就要做出一点改变,注意到for中多了一个loc[now][i]<k的情况:即当前位置自己本不应该插入,但是是个旧版本的线性基元素,为了让线性基有先后区别,只需要把当前数赶走就行了,来个乾坤大挪移:
swap两次把应该插入的数置换掉;
然后原序列的前缀线性基用到的数都是尽可能靠后的。那么如何消除影响:
统计区间异或最值的时候只需要跳过旧版本即可:
int query(int l,int r)
{
int res=0;
for(int i =29;i>=0;--i)
{
if(loc[r][i]>=l&&(res^p[r][i])>res)res^=p[r][i];
}
return res;
}
增加了一个判断:loc[r][i]>=l;
如此本题就算做完了;
总结:
前缀线性基能够维护出区间线性基,不支持删改操作,但是可以在序列后面增数。