前言
首先講一下線性基是什么東西,線性基是一個集合,你在原集合中找到一個子集,子集中的數xor起來一定能在線性基中找一個對應子集的xor和與其相等。
比如說,{x,y}和{x,x^y} 就滿足這么樣一個關系。
原理
我們把這個擴展一下,比如說我們現在有一個集合A,我新加進來一個數a,那么a與A中的數xor一下肯定是沒有問題的。
性質
定義一個數的M值為他二進制上第一個1出現的位置。
我們每往線性基中插入一個數,我們要讓這個數的M值與之前線性基中的每一個數的M值都不同。
插入
那么如何實現呢?
void insert(LL c){
for (int i=51;i>=0;i--){
if (c&bit[i]){
if (!xxj[i]){
xxj[i]=c;
break;
}
c=c^xxj[i];
}
}
}
在這里,xxj[i]表示目前線性基中M值為i的這個數是多少。
那么當我們新插入一個數C
我們從大到小枚舉C的每一個二進制位,如果當前位置上為1,如果對應的xxj[i]沒有數,那么這個數就變成xxj[i],否則xor上xxj[i],通過我們前面的原理,這樣正確性是對的,而且這樣我們再掃后面的位置時,保證出現的1就是第一個出現的
例:
xxj[3]=101
xxj[2]=0
插入110
110-->11
所以插入xxj[2]的時候M值已經為2了(這個應該比較好想)
那么我們就完成了線性基的插入,基於二進制位,所以插入的復雜度是log的,而且通過這種插入方式,我們線性基的大小就是基於二進制的位數了,log個。
合並
合並兩個線性基只需要把一個線性基暴力插入另一個即可,復雜度:線性基大小*插入復雜度,$ log_2^2$
刪除
這種不加特技的線性基不支持刪除操作
取最大值
我們從最高位倒着掃下來,掃到第i位,如果當前的答案ans這一位上為1,那么我們xor上xxj[i]一定只會變小,而且這個影響無法消除,因為xxj[i+1..n]都不可能在那ans第i位上變為1,(根據xxj[i]的性質),同理,如果ans這一位上位0,那么xor上ans[i]一定會讓答案變大。
當然如果xxj[i]==0,那就沒有影響
LL query_max(){
LL ret=0;
for (int i=51;i>=0;i--){
if ((xxj[i]^ret)>ret) ret=ret^xxj[i];
}
return ret;
}
取xor d的最大值
那么只需要把ret的初值賦為d就行了,原理也和上面的相同
取最小值
只需要找到最小的i,且xxj[i]不等於0就行了
取k小值
乍一看,一般的線性基好像不可做,
問題在哪兒?
1000001
0000001
同時選1和2比只選1要差,所以我們無法做
但是如果線性基長成這樣
1000000
0100000
0010000
0001000
0000100
那么就好做了,因為選取1和2一定比只選1要優。
所以我么需要對原來的線性基rebuild一下,使得它變成上面那樣的形式,當然
1000010
0100001
0000101
這種形式也是可以的,xxj[最后一位]上沒有數,所以同時選2和3也比只選2優,盡管最后一位上的1被消掉了
所以我們要使得若xxj[i]!=0,那么線性基里其他的數第i位上都為0,所以我們只需要拿xxj[i]去xor一下那些數就好了。
rebuild之后的線性基怎么做:把k轉成二進制,若k的第i位為1,那么將ans xor 上rebuild后第i大的xxj就行了。
void rebuild()
{
for (int i=60;i>=0;i--)
for (int j=i-1;j>=0;j--)
if (d[i]&(1LL<<j))
d[i]^=d[j];
for (int i=0;i<=60;i++)
if (d[i])
p[cnt++]=d[i];
}
long long kthquery(long long k)
{
int ret=0;
if (k>=(1LL<<cnt))
return -1;
for (int i=60;i>=0;i--)
if (k&(1LL<<i))
ret^=p[i];
return ret;
}
以上就是我對線性基的一些個人理解,希望能幫助大家學習,謝謝
同時在此感謝Yveh的博客給了很大幫助。
也感謝同學給予的幫助zhouyuheng2003