學習來自:https://www.cnblogs.com/ljh2000-jump/p/5869991.html
和 https://blog.csdn.net/a_forever_dream/article/details/83654397
此處只拿出他們做題總結的部分,原理過程刪掉了
1、線性基:
若干數的線性基是一組數a1,a2,...an,其中ax的最高位的1在第x位。
通過線性基中元素xor出的數的值域與原來的數xor出數的值域相同。
2、線性基的構造法:
對每一個數pp從高位到低位掃,掃到第x位爲1時,若ax不存在,則ax=pax=p並結束此數的掃描,否則令p=p xor ax。
3、查詢:
用線性基求這組數xor出的最大值:從高往低掃ax,若異或上ax使答案變大,則異或。
4、判斷:
用線性基求一個數能否被xor出:從高到低,對該數每個是1的位置x,將這個數異或上axax(注意異或後這個數爲1的位置和原數就不一樣了),若最終變爲0,則可被異或出。當然需要特判00(在構造過程中看是否有p變爲0即可)。例子:(11111,10001)(11111,10001)的線性基是a5=11111,a4=01110,要判斷11111能否被xor出,11111 xora5=0,則這個數後來就沒有是1的位置了,最終得到結果爲00,說明11111能被xor出。
關於線性基的模板:
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define mem(a,x) memset(a,x,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll a[N],p[100];
int n;
int insert(ll x){
for(int j=62;j>=0;j--) {
if(!(x>>j)) continue;
if(!p[j]) { p[j]=x; return 1; }
x^=p[j];
}
return 0;
}
一些性質:
你可以理解爲將一個序列處理完之後得到的產物,並且有如下性質(後面有證明):
1、原序列裏面的任意一個數都可以由線性基裏面的一些數異或得到。
2、線性基裏面的任意一些數異或起來都不能得到0 00
3、線性基裏面的數的個數唯一,並且在保持性質一的前提下,數的個數是最少的
證明性質1
我們知道了線性基的構造方法後,其實就可以很容易想到如何證明性質1了,我們設原序列裏面有一個數x xx,我們嘗試用它來構造線性基,那麼會有兩種結果——1、不能成功插入線性基;2、成功插入線性基。
分類討論一下
1、不能成功插入線性基**
什麼時候不能插入進去呢?
顯然就是它在嘗試插入時異或若干個數之後變成了0 00。
那麼就有如下式子:
x ^ d[a]^ d[b] ^ d[c] ^...=0
根據上面的那個小性質,則有:
d[a] ^ d[b] ^ d[c] ^...=x
所以,如果x不能成功插入線性基,一定是因爲當前線性基裏面的一些數異或起來可以等於x。
2、可以成功插入線性基
我們假設x插入到了線性基的第i個位置,顯然,它在插入前可能異或若干個數,那麼就有:
x ^ d[a] ^ d[b] ^ d[c]^ …=d[i]
d[i]^ d[a] ^ d[b] ^ d[c] ^ …=x
所以顯然,x此時也可以由線性基裏面的若干個數異或得到。
綜上,性質1得證
再看性質2
各位大佬肯定認爲這是一條顯然的性質啊,但爲了嚴謹一點,還是給出證明吧:
我們使用反證法
設d[a] ^ d[b]^ d[c]=0(其中d[c]比d[a]和d[b]要更晚被插入線性基)
那麼有d[a] ^ d[b]=d[c]
∵d[c] 可以由d[a]^ d[b]得到
∴d[c]不可能插入線性基
故假設不成立,所以線性基中不存在有任何數異或起來可以得到0。
最後看性質3
這個性質被BJWC拿來出過一道題,那題網上幾乎找不到證明(都是草草的給出做法然後貼代碼),然而如果你熟記這個性質3,那麼很快就能想明白那題。(這題在文章末尾會給出)
那麼我來嘗試證明性質3(畢竟網上幾乎找不到證明作爲參考,這裏筆者我只能亂推了):
還是沒什麼卵用地分類討論一下
1、假如序列裏面的所有元素都可以插入到線性基裏面
顯然如果是這種情況的話,不管是用什麼順序將序列裏的數插入到線性基裏,線性基中的元素一定與原序列元素數量相同。所以性質3成立。
2、假如序列裏面的一些元素不能插入到線性基裏面
我們設x不能插入到線性基裏面,那麼一定滿足形如d[a] d[a]d[a] ^ d[b] d[b]d[b] ^ d[c]=x d[c]=xd[c]=x的式子,那我們嘗試將插入順序改變,變成:d[a]、d[b]、x、d[c]。那麼顯然,d[c]是不可能插入成功的,簡單的證明:
d[a]^ d[b] ^ d[c]=x
d[a]^ d[b]^ x=d[c] (根據上面那條並沒有什麼卵用的異或性質)
原來是x插入不進去,改變順序後,d[c] d[c]d[c]插入不進去,也就是說,對於插入不進去的元素,改變插入順序後,要麼還是插入不進去,要麼就是插入進去了,同時另一個原來插入的進去的元素插入不進去了,所以,可以插入進去的元素數量一定是固定的。
顯然,如果你去掉線性基裏面的任意一個數,都會使得原序列裏的一些(或一個)數無法通過用線性基裏的元素異或得到,所以,每一個元素都是必要的,換句話說,這裏面沒有多餘的元素,所以,這個線性基的元素個數在保持性質1的前提下,一定是最少的。
順便貼上插入的代碼(代碼裏的ll 都是指longlong):
void add(ll x)
{
for(int i=50;i>=0;i--)
{
if(x&(1ll<<i))//注意,如果i大於31,前面的1的後面一定要加ll
{
if(d[i])x^=d[i];
else
{
d[i]=x;
break;//記得如果插入成功一定要退出
}
}
}
}
如何求最大值
完整的說,是如何求在一個序列中,取若干個數,使得它們的異或和最大。
首先構造出這個序列的線性基,然後從線性基的最高位開始,假如當前的答案異或線性基的這個元素可以變得更大,那麼就異或它,答案的初值爲0。
代碼如下:
ll ans()
{
ll anss=0;
for(int i=50;i>=0;i--)//記得從線性基的最高位開始
if((anss^d[i])>anss)anss^=d[i];
return anss;
}
如何求最小值
顯然的,最小值一定是最小的d[i]。(如果讓最小的d[i]去異或其它的d[i]一定會讓它變得更大,所以它自己就是最小的)
如何求第k小的值
完整的說,應該是——從一個序列中取任意個元素進行異或,求能異或出的所有數字中第k小的那個。
首先,要對這個序列的線性基處理一下,對於每一個d[i] d[i]d[i],枚舉j=i to1,如果d[i] (2)的第j位爲1,那麼d[i]異或d[j−1]。
那麼處理完一個線性基之後,應該大致是長這個樣子的(x表示0或1):
1xxxx0xxx0x
1xxx0x
1x
求解過程:將k先轉成二進制,假如k的第i位爲1,ans就異或上線性基中第i ii個元素(注意不是直接異或d[i-1])。
代碼如下:
void work()//處理線性基
{
for(int i=1;i<=60;i++)
for(int j=1;j<=i;j++)
if(d[i]&(1<<(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;
}
}
如何判斷一個數是否能被當前線性基中的元素異或得到
把它嘗試插入進線性基裏面去,假如可以插入,說明不能異或得到,假如插不進去,說明不能異或得到 (原理然而上面已經講了)