前言
可以將線性基理解爲是將一個序列處理完之後得到的產物。線性基是一個集合,在原集合中找到一個子集,子集中的數異或起來一定能在線性基中找一個對應子集的異或和與其相等。
比如說,{x,y} 和 {x, x^y} 就滿足這樣一個關係。
性質
- 原序列裏面的任意一個數都有唯一方案由線性基中元素異或得到。
- 在線性基中任取若干個元素,它們的異或不爲零。即它們線性無關。
- 線性基的空間唯一,並且空間最小
構造
考慮如何在已有一組線性基的情況下,向線性空間的元素集合中插入一個元素。
插入新的元素後,需要滿足:
- 線性基張成的空間中包含新插入的元素。
- 線性基仍然線性無關。
具體地,依次考慮新插入元素的每個爲1的二進制位,若線性基不存在這一位,那麼將這個新元素加入線性基中。
否則,將新元素異或上線性基的這一位,然後繼續處理下一位。
因爲插入一個元素等同於插入其異或上線性基中的一個元素,所以性質一滿足。
從構造過程中就可以看出,已有線性基無法異或得到這個新元素,所以性質二滿足。
線性基中的每個元素的二進制最高位均不同。
for(int i = 1; i <= n; i++) {
for(int j = 60; j >= 0; j--) {
if(a[i] & (1LL << j)) { // 1後面必須要加longlong,否則會爆
if(!f[i]) {
f[i] = a[i]; // f[]即爲線性基
break;
}
a[i] ^= f[i];
}
}
}
性質證明
首先,要了解異或運算具有如下性質:
- a ^ b ^ c = 0,那麼a ^ b = c
- 如果a ^ b = c,那麼a ^ c = b
性質一
原序列裏面的任意一個數都有唯一方案由線性基中元素異或得到
設原序列裏面有一個數x,嘗試用它來構造線性基,那麼會有兩種結果:
1、不能成功插入線性基;2、成功插入線性基。
如果不能成功插入線性基
當一個數不能插入時,顯然是嘗試插入它時異或若干個數之後變成了0。
那麼就有如下式子:
根據異或運算的性質則有:
所以,如果x不能成功插入線性基,一定是因爲當前線性基裏面的一些數異或起來已經可以等於x。
如果可以成功插入線性基
假設x插入到了線性基的第i個位置,那麼有:
所以顯然,x 此時也可以由線性基裏面的若干個數異或得到。
性質二
在線性基中任取若干個元素,它們的異或不爲零。即它們線性無關。
使用反證法
設(其中 f[c] 比 f[a] 和 f[b] 要晚插入線性基)
那麼有
所以 f[c] 可以由得到,所以 f[c] 不可能插入線性基
故假設不成立,所以線性基中不存在有任何數異或起來可以得到0。
性質三
線性基的空間唯一,並且空間最小
假如原序列的所有元素都可以插入到線性基裏面
如果是這種情況的話,不管是用什麼順序將序列裏的數插入到線性基裏,線性基中的元素一定與原序列元素數量相同。所以性質3成立。
假如原序列的一些元素不能插入到線性基裏面
假設x不能插入到線性基裏面,那麼一定滿足形如 f[a] ^ f[b] ^ f[c] = x
的式子
那我們嘗試將插入順序改變,變成:f[a]、f[b] 、x、f[c] 。
那麼顯然,d[c]是不可能插入成功的,因爲 f[c] 已經可以由 f[a] ^ f[b] ^ x
得到
原來是x 插入不進去,改變順序後,f[c] 插入不進去,也就是說,對於插入不進去的元素,改變插入順序後,要麼還是插入不進去,要麼就是插入進去了,同時另一個原來插入的進去的元素插不進去了,所以,可以插入進去的元素數量一定是固定的。
如果去掉線性基裏面的任意一個數,都會使得原序列裏的一些數無法通過用線性基裏的元素異或得到,所以,每一個元素都是必要的,換句話說,線性基裏面沒有多餘的元素。
線性基的應用
詢問一個數能否表示爲數組中的某些數的異或和
先求出線性基,從高位開始依次操作,如果當前位爲1,那麼就讓x異或線性基中當前位爲1的數,否則不操作。最後判斷結果是否爲0。(一個數在異或後變成了0,肯定是因爲這個數異或上了它自己)
求序列的最大/最小異或和
首先構造出這個序列的線性基,然後從線性基的最高位開始,假如當前的答案異或線性基的這個元素可以變得更大,那麼就異或它,答案的初值爲0。
LL ans = 0LL;
for(int i = 60; i >= 0; i--) // 從線性基的最高位開始
ans = max(ans, ans ^ f[i]);
本質是一個貪心的求解過程,優先使最高位儘可能大,因爲當前位來說,後面的低位無論有多少個1,貢獻也沒有當前位大
對於序列的最小異或和來說,答案顯然就是最小的 f[i]
求第k小異或和(HDU 3949)
對於一般的線性基來說
1000001
0000001
同時選1和2比只選1要差,所以一般的線性基無法做。
但是如果線性基是下面這種形式:
100000
010000
001000
000100
那麼就可做了,因爲選取1和2一定比只選1要優。
所以需要對原來的線性基重構一下,保證線性基中的每一位,只有一個數可以使當前位爲1,其他數的當前位都爲0.
首先根據性質一得到的基,最高位1的位置互不相同。 記 爲 線性基中最高位的1在第 i 位的 向量, 那麼按照 i 從大到小的順序, 用 去異或 .
對於每個 f[i] 來說,枚舉 j = 0 ~ i - 1
,如果 f[i] 的第 j 位爲1,就讓 f[i] ^ f[j]
可以發現,處理完之後,只有 的第 i 位是1,其他的第 i 位都是0。線性基中的元素,作用其實都是提供自己最高位上的1。
把 k二進制拆分,每一位的 0/1
對應異或時 選/不選
線性基存在的這一位,若 k 的第 i 位爲1,那麼 ans 就異或上線性基中第 i 個元素(注意不是異或 f[i-1])。
線性基中存在的位的 0/1
唯一確定了一個異或出的數,由於每個位只在一個基中爲 1,所以這些位組成的二進制數的大小就可以代表異或出的數的大小。
需要特別注意的是: 0能不能被異或出來的問題。線性基中只能異或出不爲0的解,如果線性基的大小和原數組一樣,0是不能被異或出來的,否則可以。
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#define pb push_back
#define fi first
#define se second
#define lowbit(x) x&(-x)
#define PII pair<int, int>
#define mst(a, b) memset(a, b, sizeof(a))
#define rush() int T;scanf("%d", &T);for(int cas = 1; cas <= T; cas++)
#define Rush() int T; cin >> T; for(int cas = 1; cas <= T; cas++)
#define FIO ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
using namespace std;
typedef double db;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const double eps = 1e-8;
const int Mod = 1e9 + 7;
const int MaxN = 5e5 + 5;
const int MaxM = 1e6 + 5;
LL a[MaxN], f[70];
int n, m, cnt = 0;
void guass() {
for(int i = 62; i >= 0; i--) f[i] = 0LL;
for(int i = 1; i <= n; i++) {
for(int j = 62; j >= 0; j--) {
if((a[i] >> j) & 1) {
if(!f[j]) {
f[j] = a[i];
break;
}
a[i] ^= f[j];
}
}
}
for(int i = 62; i >= 0; i--) {
for(int j = i + 1; j <= 62; j++)
if((f[j] >> i) & 1) f[j] ^= f[i]; // 只有f[i]的第i位爲1
}
cnt = 0;
for(int i = 0; i <= 62; i++)
if(f[i]) f[cnt++] = f[i];
}
LL getKth(LL k) {
if(cnt < n) { // 說明原序列可以異或出0
if(k == 1) return 0; // 直接返回最小值0
else k--; // 去掉0這個最小值後的第K小
}
if(k >= (1LL << cnt)) return -1;
LL ans = 0LL;
for(int i = 0; i <= 62; i++) {
if((k >> i) & 1) ans ^= f[i];
}
return ans;
}
int main()
{
rush() {
printf("Case #%d:\n", cas);
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%lld", a + i);
guass();
scanf("%d", &m);
while(m--) {
LL k; scanf("%lld", &k);
printf("%lld\n", getKth(k));
}
}
return 0;
}
其他好題
BZOJ 2115
codeforces724G Xor-matic Number of the Graph
BJWC 2011 元素
SCOI 2016 幸運數字
TJOI 2008 彩燈