學習筆記第二十三節:線性基
正題
注意!本篇文章注意區分插入和填入。
這個神奇的資料結構在很早之前就會了,現在寫部落格回憶一下。
線性基有著非常好的性質,它主要是用來解決異或和之類的問題。
他是一個什麼樣子的東西呢?
首先他是一個數組,是一個那麼大的陣列。它存些什麼呢?
我們在這裡假設就是線性基。
有兩種取值,第一種就是空;第二種是二進位制最高位為i的任意數的異或和。
先等等,這樣直接說好像沒有什麼意義。
不如我們先探求一下它的性質,再想著如何去維護?
性質1:線性基中的數可以異或出原陣列。
性質2:線性基第i個的二進位制最高位為i。
接下來,我們來研究怎麼保證這兩個性質。
插入
插入?
假設插入的數是,那麼我們找到x的二進位制最高位,如果線性基的第個數為空,那麼直接填進去。
否則,我們就異或上線性基中的第個數,那麼可以保證x的最高位肯定小於i了。
繼續往下找。
很明顯可以保證線性基的性質,如果最後沒有填進去,那麼說明線性基中的數已經可以組成x了。
程式碼<略醜>
void insert(long long*now,long long c){ for(int i=60;i>=0;i--) if(c>>i){ if(now[i]) c^=now[i]; else {now[i]=c;break;} } }
刪除
不好意思線性基不支援刪除,因為具有後效性;遇到支援刪除的線性基,儘量倒過來構造(從後往前)。
查詢最大
我們很明顯可以想到一種貪心,拿一個ans從線性基中的高位往低位掃,如果異或上當前的數可以使得答案變大,那麼我們就異或上它。否則就不異或。
證明也是極其簡單的。(因為根據性質一,線性基中的數可以異或出原陣列,所以原陣列可以異或得到的,線性基也可以異或得到。
for(int q=60;q>=0;q--) ans=max(ans,ans^now[q]);
查詢最小
分兩種情況討論。
1.線性基中的數的個數等於原陣列中的數的個數,說明,每個數都填入了一個位置,輸出線性基中非空最低位的數即可。
2.否則,一定有數沒有填入,說明,當前這個數可以被 未插入這個數的線性基組成,他們兩個異或起來等於0.最小為0.
查詢一個數是否可以被n個數異或得到
首先n個數建一個線性基,看一下這個數能否填入線性基中,如果可以,那麼就不能被n個數異或得到,否則可以。
查詢第k小
首先我們可以做一點事情使得線性基擁有美好的性質。
我們儘量讓這一位只有i這位為1。
怎麼做到?我們尋找二進位制第二高位的j,然後讓,就可以消掉j這位,繼續往下消,直到消到最小。
首先,線性基的正確性是顯然的。
如果為空呢?
那麼我們至少保證了為空。(沒問題啊。。。
第k小,我們把k二進位制拆分,如果第i位為1,那麼就異或上線性基中非空的第i位。
思考怎麼證明。第i個數如果非空,那麼肯定擁有唯一的第i位,因為上面的都被異或掉了,下面的本來就沒有。
所以不管怎麼異或, 如果線性基中第i個數是非空的,肯定只有這個數控制著第i位,所以就相當於非空的線性基組成二進位制數一樣,異或起來就行了。
注意判斷是否存在0對答案的影響
void prepare(now){
for(int i=60;i>=0;i--)
for(int j=i;j>=0;j--)
if(now[i]>>j) now[i]^=now[j];
}
int find_kth(long long k){
prepare(now);
int t=0,d[65];//t記錄有多少個非空的數位,d[i]表示第i個非空數位是哪一個
for(int i=0;i<=60;i--)
if(now[i]) d[t]=i,t++;
if(t<n) k--;//非空數位小於n,那麼前面多了一個0,k減一
if(k==0) return 0;//求的就是0
long long ans=0;
for(int i=t-1;i>=0;i--)//d的範圍是[0,t)
if(k>>i){//第i位為1,那麼就異或上有數的第i位
ans^=now[d[i]];
k^=(1<<i);//消除對後面的影響
}
return ans;
}