1. 程式人生 > >模板 - 數學 - 線性基

模板 - 數學 - 線性基

等價 快速 處理 fine 當前 () target hellip 遍歷

終於開始啃這個破東西了。

抄襲自:https://www.luogu.org/blog/Marser/solution-p3812

線性基某個問題描述:

給定n個整數(數字可能重復),求在這些數中選取任意個,使得他們的異或和最大。

簡介:

線性基是一種擅長處理異或問題的數據結構.設值域為 $[1,N]$,就可以用一個長度為 $\lceil \log_2N \rceil$ 的數組來描述一個線性基。特別地,線性基第 $i$ 位上的數二進制下最高位也為第 $i$ 位。

一個線性基滿足,對於它所表示的所有數的集合 $S$ , $S$ 中任意多個數異或所得的結果均能表示為線性基中的元素互相異或的結果。

即,線性基能使用異或運算來表示原數集使用異或運算能表示的所有數。運用這個性質,我們可以極大地縮小異或操作所需的查詢次數。

插入:

我們考慮插入的操作,令插入的數為 $x$ ,考慮 $x$ 的二進制最高位 $i$ ,

若線性基的第 $i$ 位為0,則直接在該位插入 $x$ ,退出;

若線性基的第 $i$ 位已經有值 $a_i$ ?,則 $x = x \oplus a_i $ ,重復以上操作直到 $x=0$ 。

如果退出時 $x=0$ ,則此時線性基已經可以表示原先的 $x$ 了;反之,則說明為了表示 $x$ ,往線性基中加入了一個新元素。

很容易證明這樣復雜度為 $\log_2x$ ,也可以用這種方法判斷能否通過原數列異或得到一個數 $x$ 。

查詢異或最值:

查詢最小值相對比較簡單。考慮插入的過程,因為每一次跳轉操作, $x$ 的二進制最高位必定單調降低,所以不可能插入兩個二進制最高位相同的數。而此時,線性基中最小值異或上其他數,必定會增大。所以,直接輸出線性基中的最小值即可。

考慮異或最大值,從高到低遍歷線性基,考慮到第 $i$ 位時,如果當前的答案 $x$ 第 $i$ 位為0,就將 $x$ 異或上 $a_i$ ?;否則不做任何操作。顯然,每次操作後答案不會變劣,最終的 $x$ 即為答案。

同樣,我們考慮對於一個數 $x$ ,它與原數列中的數異或的最值如何獲得。用與序列異或最大值類似的貪心即可解決。

查詢第k小值:

我們考慮進一步簡化線性基。顯然,一個線性基肯定可以表示為若幹個形如 $2^i$ 的數。從高到低處理線性基每一位,對於每一位向後掃,如果當前數第 $i$ 位為0,且線性基第 $i$ 位不為0,則將當前數異或上 $a_i$ 。這一操作可以在 $O(n^2)$ 的時間內解決。

經過這一步操作後,設線性基內共有 $cnt$ 個數,則它們共可以表示出 $2^{cnt}$ 個數。當然,對於0必須特殊考慮。如果插入的總數 $n$ 與 $cnt$ 相等,就無法表示0了。

同樣,考慮最小值時,也必須要考慮到0的情況。事實上,如果插入時出現了未被加入的元素,就肯定可以表示出0。

隨後,我們考慮將 $k$ 二進制拆分,用與快速冪類似的方法就可以求出第 $k$ 小值。

學過線性代數的同學應該可以看出,這個過程就是對一個矩陣求解異或意義下的秩的過程。因此, $cnt \leq \lceil \log_2N \rceil$ 一定成立。而最終,線性基中保存的也是異或意義下的一組極小線性無關組。

同樣,有關線性基的一切運算都可以看做矩陣的初等行列變換,也就可以將其看做線性規劃問題。同樣,可以離線使用高斯消元來構造極小線性基。

其他性質:

性質

1.設線性基的異或集合中不存在0。

2.線性基的異或集合中每個元素的異或方案唯一,其實這個跟性質1是等價的。

3.線性基二進制最高位互不相同。

4.如果線性基是滿的,它的異或集合為[1,2n−1]。

5.線性基中元素互相異或,異或集合不變。

查詢能表示的最大異或和代碼:

#include<bits/stdc++.h>
using namespace std;
#define ll long long

const int MN=60;
ll a[61],tmp[61];

bool flag;//能否表示0

//嘗試向線性基中插入一個值
void ins(ll x) {
    for(int i=MN; ~i; i--)
        if(x&(1ll<<i))
            if(!a[i]) {
                a[i]=x;
                return;
            } else
                x^=a[i];
    flag=true;
}

//判斷當前值能否被線性基表示
bool check(ll x) {
    for(int i=MN; ~i; i--)
        if(x&(1ll<<i))
            if(!a[i])
                return false;
            else
                x^=a[i];
    return true;
}

//查詢線性基能表示的最大值
ll qmax(ll res=0) {
    for(int i=MN; ~i; i--)
        res=max(res,res^a[i]);
    return res;
}

//查詢線性基能表示的最小值
ll qmin() {
    if(flag)
        return 0;
    for(int i=0; i<=MN; i++)
        if(a[i])
            return a[i];
}

//查詢線性基能表示的第k小值
ll query(ll k) {
    ll res=0;
    int cnt=0;
    k-=flag;
    if(!k)
        return 0;
    for(int i=0; i<=MN; i++) {
        for(int j=i-1; ~j; j--)
            if(a[i]&(1ll<<j))
                a[i]^=a[j];
        if(a[i])
            tmp[cnt++]=a[i];
    }
    if(k>=(1ll<<cnt))
        return -1;
    for(int i=0; i<cnt; i++)
        if(k&(1ll<<i))
            res^=tmp[i];
    return res;
}

int main() {
    int n;
    ll x;
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
        scanf("%lld",&x),ins(x);
    printf("%lld\n",qmax());
    return 0;
}

瞎套用居然過了的:

#include<bits/stdc++.h>
using namespace std;
#define ll long long

const int MN=60;
ll a[61],tmp[61];

bool flag;//能否表示0

//嘗試向線性基中插入一個值
void ins(ll x) {
    for(int i=MN; ~i; i--)
        if(x&(1ll<<i))
            if(!a[i]) {
                a[i]=x;
                return;
            } else
                x^=a[i];
    flag=true;
}

//判斷當前值能否被線性基表示
bool check(ll x) {
    for(int i=MN; ~i; i--)
        if(x&(1ll<<i))
            if(!a[i])
                return false;
            else
                x^=a[i];
    return true;
}

//查詢線性基能表示的最大值
ll qmax(ll res=0) {
    for(int i=MN; ~i; i--)
        res=max(res,res^a[i]);
    return res;
}

//查詢線性基能表示的最小值
ll qmin() {
    if(flag)
        return 0;
    for(int i=0; i<=MN; i++)
        if(a[i])
            return a[i];
}

//查詢線性基能表示的第k小值
ll query(ll k) {
    ll res=0;
    int cnt=0;
    k-=flag;
    if(!k)
        return 0;
    for(int i=0; i<=MN; i++) {
        for(int j=i-1; ~j; j--)
            if(a[i]&(1ll<<j))
                a[i]^=a[j];
        if(a[i])
            tmp[cnt++]=a[i];
    }
    if(k>=(1ll<<cnt))
        return -1;
    for(int i=0; i<cnt; i++)
        if(k&(1ll<<i))
            res^=tmp[i];
    return res;
}

int main() {
    int n,m;
    int cnt=0;
    scanf("%d%d",&n,&m);
    for(int i=1; i<=m; i++){
        ll x=0;
        char c[61];
        scanf("%s",c+1);
        for(int j=1;j<=n;j++){
            x<<=1;
            if(c[j]==O){
                x|=1;
            }
        }
        if(check(x)==0){
            cnt++;
            ins(x);
        }
    }
    printf("%lld\n",(1ll<<cnt)%2008);
    return 0;
}

有多少個線性基,就有多少2的多少次方種表示(若我們的線性基可以表示0,則包括0)。

不是很懂,感覺ins函數中途返回的話就是插入一個新的值,到最後才返回的話說明這個數可以被表示。

提高以下的題目就這樣沒有了……

https://www.luogu.org/problemnew/show/P3292

先找到樹根,從樹根開始遍歷每個點,類似LCA的處理,記錄他們每個點的高度為2的i次方的祖先以及到這個祖先的線性基最大值。最後是不是直接兩個點查LCA然後找出他們到LCA的線性基最大值直接異或就可以了?好像錯了,不能直接最大值異或。

記錄每個結點到根的線性基,然後查詢得時候求出LCA合並線性基?

補一個合並線性基的代碼:(假如某個線性基的一項不能被另一個線性基表示,則插入這一項)

void merge(ll *d,ll *s1, ll* s2){
    for(int i=0;i<=MN;i++)
        g[i]=s1[i];//復制線性基s1
    for(int i=0;i<=MN;i++)
        ins(g,s2[i]);//不用check直接試插就可以
}

模板 - 數學 - 線性基