1. 程式人生 > 其它 >線性基(Linear Basis)學習筆記

線性基(Linear Basis)學習筆記

前言

我看網路上沒有什麼非常系統的教學,可能是我太菜了吧,現在才學,做個記錄給自己看。

簡略介紹

一個數集能兩兩異或,能表出許多新的數。

線性基是一個集合,能夠在記錄最少的數的基礎上,表示出一個等價的異或集合。+

常用來解決最大異或子集問題。

下文假設 \(L\) 為值域最大值在二進位制下的位數。

構造方法 & 解決問題

插入

bool insert(ll val) {
	fd(i, L, 0)
		if (val >> i & 1)) {
			if (!b[i]) { // b[i] 是記錄線性基的陣列
				b[i] = val;
				break;
			}
			val ^= b[i];
		}
	return val;
}

如果說 \(\text{val}\) 能被表出,那麼它一定會在最後變成 \(0\) 。否則,我們認定下標 \(i\) 的位置放置的數的二進位制下第 \(i\) 位一定是 \(1\)。並將它插入。

不難發現,一個插入的數被填進第 \(i\) 位時,其更高位一定被全部異或 \(0\) ,故 \(i\) 是它的最高位 \(1\) 的位置。記住這個性質,會在下面使用。

插入一個數複雜度是 \(O(L)\) 的。

最大異或子集

根據上面的性質,我們從高位貪心地考慮,希望能夠儘量讓高位的 \(1\) 能夠出現。

ll mx() {
    ll ret=0;
    fd(i, L, 0)
        if((ret ^ b[i]) > ret)
            ret ^= b[i];
    return ret;
}

\(O(L)\)

合併兩個線性基

直接把一個線性基中的元素插入另一個,\(O(L^2)\)

求第 \(k\) 小能被表出元素

我們改造這個線性基,使得每一位相互獨立。類似高斯消元。從低位到高位消。

void rebuild() {
	fo(i, 1, L)
		fo(j, 1, i)
			if(d[i] >> (j - 1) & 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的解
	// 記得先 rebuild
	ll ret = 0;
	fo(i, 1, L)
        if(d[i]) {
            if(k & 1)	ret ^= d[i];
            k >>= 1;
        }
   	return ret;
}

順便一提,實際上 \(\text{rebuild}\) 之後的線性基是完全等價的,可以正常做其他操作。

刪除

線上的做法太複雜了一般不考不是很優美,直接說離線吧。

線上性基的每一個位置維護一個最晚插入時間 \(t\) ,那麼插入的時候

FOR i=L~0
如果 目前這一位線性基為空
則將目前這一位的線性基附為 (v1,t1)
否則:
將目前這一位的線性基記為 (v2,t2)
      如果 t2<t1:
          將目前這一位的線性基替換為 (v1,t1)
          v2^=v1
          用(v2,t2)插入下一位線性基
      否則:
          v1^=v2
          用(v1,t1)插入下一位線性基

在查詢的時候只需要看 \(t \ge t_0\) 的位置就好了。