線性基(處理集合異或的強力工具)
看了好多篇關於線性基的部落格,只是說明了怎麼求線性基,但是大都沒有說明為什麼這樣求線性基。
定義:
有一個集合 S = {a1,a2...,an},T的滿足下面條件的一個最小子集A = {a1,a2,....,ak}
A的所有子集的異或和的值域與T的所有子集的異或和的值域相同,那麼A就是T的線性基。
預備知識:
1、張成:S的所有子集,其異或和的所有可能的結果組成的集合,為S的張成,記作span(S)。
2、線性相關:對於一個集合S,如果存在一個元素Sj,去除這個元素後得到的集合S'的張成span(S')中包含Sj,即SJ∈span(S'),則稱集合S是線性相關的。如果不存在這樣的Sj,那麼集合S就是線性無關
3、線性基:有了上面兩個名詞,我們還可以這樣定義線性基。
(1)A ⊆ span(S)
(2)A是線性無關的
則集合A是集合S的線性基。
性質:
1、A是一個集合的線性基,那麼它的任何真子集都不可能是線性基;
2、S中所有的向量都可以按唯一的方式表達為 A 中元素的線性組合(也就是異或和)。
構造:
我們令集合中的數為a1,a2,....,an,b[ ]陣列用來儲存線性基裡面的數。(下面的二進位制的位數下標從0開始)
第一種情況:找到ai的最高位,假如是第j位,如果b[j]還沒有數,即b[j] = 0,那麼現在b陣列中的數的張成肯定不包含ai,那麼我們就可以b[j] = a[i],然後利用已經線上性基裡的最高位小於j的把b[j]二進位制中的1給消掉,具體就是 b[j] ^= b[k] ( k < j && b[j]的第k位為1 && b[k] != 0) ,然後用同樣的方法把大於j的也消掉。
第二種情況:找到ai的最高位,假如是第j位,如果b[j]已經有數,就判斷現線上性基的張成span(a1,a2...,ai-1)包不包含ai,如果包含,那麼ai就沒有必要加進線性基。怎麼判斷呢,我們如果ai的第j位為1且b[j] != 0,那麼我們就把ai的值異或上b[j],依次往後判斷,直到ai當前的最高位對應的b[j] == 0,就可以把ai加入線性基,執行上面第一種情況的操作,或者ai為0,就丟掉ai。
程式碼如下:
void create() { for(int i=1;i<=n;i++) { for(int j=60;j>=0;j--) { if((1LL<<j)&a[i]) { if(b[j] != 0) a[i] ^= b[j]; else { b[j] = a[i]; for(int k=j-1;k>=0;k--) if(((1LL<<k)&b[j]) && b[k]) b[j] ^= b[k]; for(int k=j+1;k<=60;k++) if(((1LL<<j)&b[k])) b[k] ^= b[j]; break; } } } } }
這段程式碼是維護一個對角矩陣,加入一行之後,先用下面的行消自己,然後再用自己去消上面的行。
我們來演示一下這個過程:
加入n = 5,a = {7,1,3,4,5}
初始矩陣:
0 0 0
0 0 0
0 0 0
插入7之後:
1 1 1
0 0 0
0 0 0
插入3之後,為了維護對角矩陣,把7的低位消掉:
1 0 0
0 1 1
0 0 0
插入1之後,把1上面的行的低位都消掉:
1 0 0
0 1 0
0 0 1
然後就發現後面那幾個數都已經包含在b陣列的張成裡了,加不進去了。
上述過程是把線性基維護成一個對角矩陣,其實我們還有一種程式碼量比較少的線性基的構造方法,就是隻把矩陣消成上三角矩陣,這樣的話同樣可以知道哪一位存在於線性基內。
線性基的操作:
我們把線性基封裝成一個結構體,這樣使用起來方便一點:
struct LineBasis
{
LL b[66];
LL p[66];
int cnt;
int max_b = 62;
LineBasis()
{
memset(b,0,sizeof(b));
memset(p,0,sizeof(p));
cnt = 0;
}
}
這裡的b陣列就是用於存線性基裡的數,這個cnt是記錄線性基裡面有多少個數。
那麼這個cnt有什麼作用呢,2的cnt次冪就是這個線性基所有子集異或和能構成的不同元素的個數(這裡包括零)。
max_b是最大那個數二進位制的長度
下面介紹他的各個函式:
1、插入
上面雖然展示了一種線性基的構造方法,那個方法可以提現出線性基的性質,但是下面我們用它比較方便的寫法:
bool Insert(LL val)
{
for(int i=max_b;i>=0;i--)
{
if((1LL<<i)&val)
{
if(b[i] == 0)
{
b[i] = val;
break;
}
val ^= b[i];
}
}
if(val > 0)
cnt++;
return val > 0;
}
2、合併:
把一個線性基裡的元素一個一個的Insert到另一個裡面,就完成了合併。
LineBasis Merge(LineBasis n1,LineBasis n2)
{
LineBasis ret = n1;
for(int i=0;i<=max_b;i++)
if(n2.b[i])
ret.Insert(n2.b[i]);
return ret;
}
線性基的用途:
1、求一組數所有組合能構成的不同異或和的個數:
求出這組數的線性基,線性基裡數的個數為cnt,答案就為2的cnt次冪
2、存在性:
查詢x是否存在於異或集合中,跟上面構造方法的思想相似,從高位到低位掃描x位1的二進位制位,掃到第i位時,x ^= b[i],如果最後x變為0,則存在,否則不存在。
3、最大值:
求異或集合中的最大值
如果消成對角矩陣的話,直接把線性基中的所有元素異或起來即可。但是對於上三角矩陣,異或之前判斷一下是否能變大。
還可以求一個數x與集合中某些數異或的最大值,只用把初值設為x就行了,單純求最大值時最初始值設為0.
LL QueryMax(LL x)
{
LL ans = x;
for(int i=max_b;i>=0;i--)
if((ans^b[i]) > ans)
ans ^= b[i];
return ans;
}
4、最小值:
最小值就是最低位上的線性基。
LL QueryMin()
{
for(int i=0;i<=max_b;i++)
if(b[i])
return b[i];
return 0;
}
5、k小值
這時候用構造出的上三角矩陣就不能解決這個問題了,我們要把上三角矩陣變換成對角矩陣,然後再把不為零的都按順序拿出來。這時候矩陣已經變成對角矩陣(至少是行最簡形矩陣),我們異或上某一行的值,答案就會變大一點。我們可以想象,從一個數組 a = {8,4,2,1}中選出幾個,求能組成第k小的值是多少,利用二進位制的性質,如果k的二進位制第i位為1,我們就加上數組裡第i大的數。這裡的異或上一個值也會變大一點,所以可以用同樣的思想。具體看下面程式碼
void rebuild()
{
for(int i=max_b;i>=0;i--)
for(int j=i-1;j>=0;j--)
if(b[i]&(1LL<<j))
b[i] ^= b[j];
cnt = 0;
for(int i=0;i<=max_b;i++)
if(b[i])
p[cnt++] = b[i];
}
LL kthquery(LL k)
{
LL ans = 0;
if(k>=(1LL<<cnt))
return -1;
for(int i=max_b;i>=0;i--)
if(k&(1LL<<i))
ans ^= p[i];
return ans;
}
總的模板:
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
struct LineBasis
{
LL b[66];
LL p[66];
int cnt;
int max_b = 62;
LineBasis()
{
memset(b,0,sizeof(b));
memset(p,0,sizeof(p));
cnt = 0;
}
bool Insert(LL val)
{
for(int i=max_b;i>=0;i--)
{
if((1LL<<i)&val)
{
if(b[i] == 0)
{
b[i] = val;
break;
}
val ^= b[i];
}
}
if(val > 0)
cnt++;
return val > 0;
}
LineBasis Merge(LineBasis n1,LineBasis n2)
{
LineBasis ret = n1;
for(int i=0;i<=max_b;i++)
if(n2.b[i])
ret.Insert(n2.b[i]);
return ret;
}
LL QueryMax(LL x)
{
LL ans = x;
for(int i=max_b;i>=0;i--)
if((ans^b[i]) > ans)
ans ^= b[i];
return ans;
}
LL QueryMin()
{
for(int i=0;i<=max_b;i++)
if(b[i])
return b[i];
return 0;
}
void rebuild()
{
for(int i=max_b;i>=0;i--)
for(int j=i-1;j>=0;j--)
if(b[i]&(1LL<<j))
b[i] ^= b[j];
cnt = 0;
for(int i=0;i<=max_b;i++)
if(b[i])
p[cnt++] = b[i];
}
LL kthquery(LL k)
{
LL ans = 0;
if(k>=(1LL<<cnt))
return -1;
for(int i=max_b;i>=0;i--)
if(k&(1LL<<i))
ans ^= p[i];
return ans;
}
};
int a[11000];
int main(void)
{
int n,i,j;
LineBasis s;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
s.Insert(a[i]);
}
return 0;
}
/*
5
7 1 4 3 5
*/
如有錯誤,歡迎指出。