数据结构与算法:线性基

前言

这玩意还是挺重要的,很多地方都见到了。

一、异或空间线性基

对于一堆数字能得到的所有非 0 异或和,异或空间的线性基就是在能得到同样结果的基础上,使用的元素个数最少的集合。举个例子,对于 a=[1,2,3] 这个集合,其能得到的所有异或和就是 [1,2,3]。因为只使用 [1,2] 也能得到所有异或和,所以 [1,2] 就是集合 a 的异或空间线性基。

1.几个结论

首先,不难想到,对于集合中的任意两数 a 和 b,完全可以用 a^b 来替换 a 或 b。这样完全不会影响非 0 异或和的组成,因为每次多异或一下另一个数就能还原。

之后,对于集合中的任意两数 a 和 b,若 a^b=0,那么就可以舍弃掉 a 或 b。因为异或等于 0 就相当于两数完全相等,所以可以舍弃其中任意一个。类似地,若存在 a^b^c=0,那么同样可以舍弃三者中的任意一个。

此外,因为线性基只能保证原来的非 0 异或和全能得到,所以对于 0 需要单独标记。

2.普通消元法

对于普通消元法,首先需要维护出线性基每一位代表的数,除数全是 0。之后按顺序考察每个数,对于当前数,从高位到低位考察。若当前位是 1,那么若线性基当前位是 0,那么就直接插入。否则,即线性基当前位是 1,那么就让当前数异或线性基中这一位的数,然后继续去低位考察,直到当前数为 0。最后,若线性基的数字个数正好等于原始的数字个数,那么就说明在消元的过程中没有数因为被消成 0 而舍去,也就是说原始数不可能异或出 0。反之若线性基的个数少于原始个数,那么就说明原始数可以异或出 0。

普通消元法不仅可以得到一组线性基,还能知道非 0 异或和的个数。因为线性基中的每个数都可以要或不要,所以若线性基大小为 n,那么个数就是 2 的 n 次方减一。同时,因为知道了线性基中的每个数,所以还可以知道最大的异或和。方法就是从高位到低位依次考察每个线性基,若能把当前异或和更新得更大就异或。

3.高斯消元法

高斯还是太超模了……

和本体类似,以原始数为行,每一位为列,然后从高位到低位考虑。若有数字当前位是 1,那么就把这个数字 swap 上来,然后和其他所有这位是 1 的数异或,然后去下一位。注意,在这里不用给自由元留位置,直接交换上来即可。那么就是维护一个主元区域,每次只在剩下区域里考察即可。由于是按位考虑,所以整个过程是 O(n*logV) 的。

高斯消元不仅能做到所有普通消元能做到的事,还能具体知道第 k 小的异或和。方法就是,首先先从下往上给每个基从 0 开始编号,然后将 k 拆成二进制位,第 k 小的异或和就是 k 二进制所有是 1 的位对应编号的数的异或和。

4.线性基

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

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

struct Basis
{
public:
    bool insert(ll x,vector<ll>&ans){
        for(int i=BIT;i>=0;i--){
            if(x>>i&1){   
                if(ans[i]==0){
                    ans[i]=x;
                    return true;
                }
                x^=ans[i];
            }
        }
        return false;
    }

    //普通消元法
    pair<vector<ll>,bool> normal(int n,const vector<ll>&a){
        vector<ll>ans(BIT+1);
        bool zero=false;
        for(int i=1;i<=n;i++){
            if(!insert(a[i],ans)){
                zero=true;
            }
        }
        return {ans,zero};
    }

    //高斯消元法 -> 可以求出第k大的异或和
    pair<vector<ll>,bool> gauss(int n,const vector<ll>&a){
        vector<ll>ans(n+1);
        for(int i=1;i<=n;i++){
            ans[i]=a[i];
        }

        int len=1;
        for(int i=BIT;i>=0;i--){
            for(int j=len;j<=n;j++){
                if(ans[j]>>i&1){
                    swap(ans[j],ans[len]);
                    break;
                }
            }

            if(ans[len]>>i&1){
                for(int j=1;j<=n;j++){
                    if(j!=len&&(ans[j]>>i&1)){
                        ans[j]^=ans[len];
                    }
                }
                len++;
            }
        }
        len--;
        
        return {ans,len!=n};
    }

private:
    const int BIT=63;
}B;

void solve()
{
    int n;
    cin>>n;
    vector<ll>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }

    auto [res,zero]=B.normal(n,a);

    ll ans=0;
    for(int i=50;i>=0;i--)
    {
        ans=max(ans,ans^res[i]);
    }
    cout<<ans<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    //cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

纯模板,没啥好说的。

5.k 大异或和

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

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

struct Basis
{
public:
    bool insert(ll x,vector<ll>&ans){
        for(int i=BIT;i>=0;i--){
            if(x>>i&1){   
                if(ans[i]==0){
                    ans[i]=x;
                    return true;
                }
                x^=ans[i];
            }
        }
        return false;
    }

    //普通消元法
    //返回一组线性基和是否有0
    pair<vector<ll>,bool> normal(int n,const vector<ll>&a){
        vector<ll>ans(BIT+1);
        bool zero=false;
        for(int i=1;i<=n;i++){
            if(!insert(a[i],ans)){
                zero=true;
            }
        }
        return {ans,zero};
    }

    struct Node
    {
        vector<ll>a;
        bool zero;
        int len;
    };

    //高斯消元法 -> 可以求出第k大的异或和
    //返回一组线性基,是否有0和线性基个数
    Node gauss(int n,const vector<ll>&a){
        vector<ll>ans(n+2);
        for(int i=1;i<=n;i++){
            ans[i]=a[i];
        }

        int len=1;
        for(int i=BIT;i>=0;i--){
            for(int j=len;j<=n;j++){
                if(ans[j]>>i&1){
                    swap(ans[j],ans[len]);
                    break;
                }
            }

            if(ans[len]>>i&1){
                for(int j=1;j<=n;j++){
                    if(j!=len&&(ans[j]>>i&1)){
                        ans[j]^=ans[len];
                    }
                }
                len++;
            }
        }
        len--;
        return {ans,len!=n,len};
    }

private:
    const int BIT=63;
}B;

void solve()
{
    int n;
    cin>>n;
    vector<ll>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }

    auto [res,zero,len]=B.gauss(n,a);

    int m;
    cin>>m;
    ll k;
    while(m--)
    {
        cin>>k;

        if(zero&&k==1)
        {
            cout<<0<<endl;
            continue;
        }

        if(zero)
        {
            k--;
        }

        if(k>=(1ll<<len))
        {
            cout<<-1<<endl;
            continue;
        }

        ll ans=0;
        for(int i=len,j=0;i>=1;i--,j++)
        {
            if(k>>j&1)
            {
                ans^=res[i];
            }
        }
        cout<<ans<<endl;
    }
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    //cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

也是纯模板,把第 k 大减一下转化成第 k 小即可。

cpp 也太阴了,ans 数组 n+1 位置没初始化成 0 还会因为垃圾值导致消元后的秩大于 n 的,还必须得多开一个位置才行。

6.元素

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

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

struct Basis
{
public:
    bool insert(ll x,vector<ll>&ans){
        for(int i=BIT;i>=0;i--){
            if(x>>i&1){   
                if(ans[i]==0){
                    ans[i]=x;
                    return true;
                }
                x^=ans[i];
            }
        }
        return false;
    }

    //普通消元法
    //返回一组线性基和是否有0
    pair<vector<ll>,bool> normal(int n,const vector<ll>&a){
        vector<ll>ans(BIT+1);
        bool zero=false;
        for(int i=1;i<=n;i++){
            if(!insert(a[i],ans)){
                zero=true;
            }
        }
        return {ans,zero};
    }

    struct Node
    {
        vector<ll>a;
        bool zero;
        int len;
    };

    //高斯消元法 -> 可以求出第k大的异或和
    //返回一组线性基,是否有0和线性基个数
    Node gauss(int n,const vector<ll>&a){
        vector<ll>ans(n+2);
        for(int i=1;i<=n;i++){
            ans[i]=a[i];
        }

        int len=1;
        for(int i=BIT;i>=0;i--){
            for(int j=len;j<=n;j++){
                if(ans[j]>>i&1){
                    swap(ans[j],ans[len]);
                    break;
                }
            }

            if(ans[len]>>i&1){
                for(int j=1;j<=n;j++){
                    if(j!=len&&(ans[j]>>i&1)){
                        ans[j]^=ans[len];
                    }
                }
                len++;
            }
        }
        len--;
        return {ans,len!=n,len};
    }

private:
    const int BIT=63;
}B;

void solve()
{
    int n;
    cin>>n;
    vector<vector<ll>>a(n+1,vector<ll>(2));
    for(int i=1;i<=n;i++)
    {
        cin>>a[i][0]>>a[i][1];
    }

    sort(a.begin()+1,a.end(),[&](auto &x,auto &y)
    {
        return x[1]>y[1];
    });

    vector<ll>mat(64);

    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        if(B.insert(a[i][0],mat))
        {
            ans+=a[i][1];
        }
    }
    cout<<ans<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    //cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

首先,因为要找出一种选择方式,使得异或和不等于 0,那么这个就可以用线性基来解决了。又因为要求魔力值最大,所以可以考虑先按魔力值从大到小排序,再按这个顺序插入线性基。

最难的是这个贪心的证明,就是证明不可能存在一个魔力值更大的矿石,选择了以后就没法选更小的两块了,而这两块的累加和又大于更大的这块。设更大的为 A,两块小的分别为 B 和 C,那么如果这种情况发生,那么首先说明 B 和 C 可以同时选,也就是异或不为 0,又说明 A 和这两个的异或都为 0。此时就可以发现,因为 A^B=0 且 A^C=0,那么必然存在 B^C=0,这就矛盾了,所以这种情况不可能发生。

7.彩灯

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

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

struct Basis
{
public:
    bool insert(ll x,vector<ll>&ans){
        for(int i=BIT;i>=0;i--){
            if(x>>i&1){   
                if(ans[i]==0){
                    ans[i]=x;
                    return true;
                }
                x^=ans[i];
            }
        }
        return false;
    }

    //普通消元法
    //返回一组线性基和是否有0
    pair<vector<ll>,bool> normal(int n,const vector<ll>&a){
        vector<ll>ans(BIT+1);
        bool zero=false;
        for(int i=1;i<=n;i++){
            if(!insert(a[i],ans)){
                zero=true;
            }
        }
        return {ans,zero};
    }

    struct Node
    {
        vector<ll>a;
        bool zero;
        int len;
    };

    //高斯消元法 -> 可以求出第k大的异或和
    //返回一组线性基,是否有0和线性基个数
    Node gauss(int n,const vector<ll>&a){
        vector<ll>ans(n+2);
        for(int i=1;i<=n;i++){
            ans[i]=a[i];
        }

        int len=1;
        for(int i=BIT;i>=0;i--){
            for(int j=len;j<=n;j++){
                if(ans[j]>>i&1){
                    swap(ans[j],ans[len]);
                    break;
                }
            }

            if(ans[len]>>i&1){
                for(int j=1;j<=n;j++){
                    if(j!=len&&(ans[j]>>i&1)){
                        ans[j]^=ans[len];
                    }
                }
                len++;
            }
        }
        len--;
        return {ans,len!=n,len};
    }

private:
    const int BIT=63;
}B;

void solve()
{
    int n,m;
    cin>>n>>m;
    vector<ll>a(m+1);
    for(int i=1;i<=m;i++)
    {
        string s;
        cin>>s;
        for(int j=0;j<n;j++)
        {
            if(s[j]=='O')
            {
                a[i]|=1ll<<j;
            }
        }
    }

    auto [res,zero,len]=B.gauss(m,a);

    cout<<((1ll<<len)%2008)<<endl;

}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    //cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

看上去这个题和线性基没啥关系,但分析就一下就会发现完全是板子题。

首先,对于开关问题,还是考虑转化成异或问题。那么对于每个开关能控制的灯,可以将其看作一个二进制为该状态的数。那么对于所有的开关,其能按出的所有组合,就是这些数能异或出来的异或和的个数。那么就只需要对这些数求一组线性基,然后输出 2 的 len 次方即可。

二、向量空间线性基——装备购买

eps 写 1e-7 过不了写 1e-5 才能过,是有什么毛病吗……

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

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

struct Basis
{
public:
    struct Node
    {
        vector<ll>a;
        bool zero;
        int len;
    };

    //异或
    bool insert(ll x,vector<ll>&ans){
        for(int i=BIT;i>=0;i--){
            if(x>>i&1){   
                if(ans[i]==0){
                    ans[i]=x;
                    return true;
                }
                x^=ans[i];
            }
        }
        return false;
    }

    //普通消元解决异或空间线性基
    //返回一组线性基、是否有0、线性基个数
    Node normal_eor(int n,const vector<ll>&a){
        vector<ll>ans(BIT+1);
        bool zero=false;
        int len=0;
        for(int i=1;i<=n;i++){
            if(!insert(a[i],ans)){
                zero=true;
            }
            else{
                len++;
            }
        }
        return {ans,zero,len};
    }

    //高斯消元解决异或空间线性基 -> 可以求出第k大的异或和
    //返回一组线性基、是否有0、线性基个数
    Node gauss_eor(int n,const vector<ll>&a){
        vector<ll>ans(n+2);
        for(int i=1;i<=n;i++){
            ans[i]=a[i];
        }

        int len=1;
        for(int i=BIT;i>=0;i--){
            for(int j=len;j<=n;j++){
                if(ans[j]>>i&1){
                    swap(ans[j],ans[len]);
                    break;
                }
            }

            if(ans[len]>>i&1){
                for(int j=1;j<=n;j++){
                    if(j!=len&&(ans[j]>>i&1)){
                        ans[j]^=ans[len];
                    }
                }
                len++;
            }
        }
        len--;
        return {ans,len!=n,len};
    }

    //向量
    bool insert(int i,int m,vector<vector<double>>&mat,vector<double>&ans){
        for(int j=1;j<=m;j++){
            if(abs(mat[i][j])>=eps){
                if(ans[j]==0){
                    ans[j]=i;
                    return true;
                }

                double rate=mat[i][j]/mat[ans[j]][j];
                for(int k=j;k<=m;k++){
                    mat[i][k]-=rate*mat[ans[j]][k];
                }
            }
        }
        return false;
    }

    //普通消元解决向量空间线性基
    //返回一组线性基、线性基个数
    pair<vector<double>,int> normal_vec(int n,int m,vector<vector<double>>&mat){
        vector<double>ans(m+1);
        int len=0;
        for(int i=1;i<=n;i++){
            if(insert(i,m,mat,ans)){
                len++;
            }
        }
        return {ans,len};
    }

private:
    const int BIT=63;
    const double eps=1e-5;
}B;

void solve()
{
    int n,m;
    cin>>n>>m;
    vector<vector<double>>mat(n+1,vector<double>(m+2));
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            cin>>mat[i][j];
        }
    }
    for(int i=1;i<=n;i++)
    {
        cin>>mat[i][m+1];
    }

    sort(mat.begin()+1,mat.end(),[&](auto &x,auto &y)
    {
        return x[m+1]<y[m+1];
    });

    vector<double>tmp(m+1);
    int cnt=0;
    int cost=0;
    for(int i=1;i<=n;i++)
    {
        if(B.insert(i,m,mat,tmp))
        {
            cnt++;
            cost+=(int)mat[i][m+1];
        }
    }
    cout<<cnt<<" "<<cost<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    //cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

这个题其实读完就能想到是要选出一个最大的线性无关组,那么就可以想到在向量空间去跑线性基,那么最后需要购买的数量就是线性基的数量。对于花费最少,其实和之前那个矿石魔力值的题类似,就是一开始按花费从小到大排序,以这个顺序往线性基里插入即可。

三、线性基的应用

1.P 哥的桶

板子仙人()

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

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

template<int BIT,typename T=ll>
struct Xor_Basis{
    int cnt;
    bool zero;
    bool is_rebuilt;
    T data[BIT+1];

    Xor_Basis(){reset();}

    void reset(){
        cnt=0;
        zero=false;
        is_rebuilt=false;
        for(int i=0;i<=BIT;i++)data[i]=0;
    }

    bool insert(T x){
        for (int i=BIT;i>=0&&x;i--){
            if(x>>i&1){
                if(data[i]==0){
                    data[i]=x;
                    cnt++;
                    is_rebuilt=false;
                    return true;
                }
                x^=data[i];
            }
        }
        zero=true;
        return false;
    }

    void rebuild(){
        if(is_rebuilt){
            return;
        }

        for (int i=BIT;i>=0;i--){
            if(data[i]==0){
                continue;
            }
            
            for(int j=i-1;j>=0;j--){
                if(data[i]>>j&1){
                    data[i]^=data[j];
                }
            }
        }
        is_rebuilt=true;
    }

    T query_max(T x=0){
        for(int i=BIT;i>=0;i--){
            if(~(x >> i)&1){
                x^=data[i];
            }
        }
        return x;
    }

    T query_min(T x=0){
        for(int i=BIT;i>=0;i--){
            if((x>>i)&1){
                x^=data[i];
            }
        }
        return x;
    }

    //kth minimum
    T query_kth(ll k){
        rebuild();

        if(zero){
            k--;
        }
        if(k<0||k>=(1ll<<cnt)){
            return -1;
        }

        T res=0;
        for(int i=0;i<=BIT;i++){
            if(data[i]){
                if(k&1){
                    res^=data[i];
                }
                k>>=1;
            }
        }
        return res;
    }

    ll query_rank(T x){
        rebuild();
        
        ll res=0,k=1;
        for(int i=0;i<=BIT;i++){
            if(data[i]){
                if(x>>i&1){
                    res|=k;
                }
                k<<=1;
            }
        }
        return res+zero;
    }

    //线性基合并
    friend constexpr Xor_Basis operator|(const Xor_Basis &lhs, const Xor_Basis &rhs){
        Xor_Basis res=lhs;
        for(int i=0;i<=BIT;i++){
            if(rhs.data[i]){
                res.insert(rhs.data[i]);
            }
        }
        res.zero=lhs.zero|rhs.zero;
        return res;
    }

    constexpr Xor_Basis& operator|=(const Xor_Basis &rhs){
        for(int i=0;i<=BIT;i++){
            if(rhs.data[i]){
                insert(rhs.data[i]);
            }
        }
        return *this;
    }

    //线性基求交
    friend constexpr Xor_Basis operator&(const Xor_Basis &lhs, const Xor_Basis &rhs){
        using i128=__int128_t;

        Xor_Basis<BIT*2,i128>tmp;
        for(int i=0;i<=BIT;i++){
            tmp.insert(((i128)lhs.data[i]<<BIT)|lhs.data[i]);
            tmp.insert(rhs.data[i]<<BIT);
        }
        Xor_Basis res;
        for(int i=0; i<=BIT;i++){
            if(tmp.data[i]){
                res.data[i]=tmp.data[i];
                res.cnt++;
            }
        }
        return res;
    }
};

template<typename T>
struct Segment_Tree
{
    vector<T>tree;
    //自定义

    Segment_Tree(int n){
        tree.resize(n<<2);
    }

    void add(int jobi,int jobv,int l,int r,int i){

        tree[i].insert(jobv);

        if(l<r)
        {
            int m=(l+r)>>1;

            if(jobi<=m){
                add(jobi,jobv,l,m,i<<1);
            }
            else{
                add(jobi,jobv,m+1,r,i<<1|1);
            }
        }
    }

    T query(int jobl,int jobr,int l,int r,int i){
        if(jobl<=l&&r<=jobr){
            return tree[i];
        }
        
        int m=(l+r)>>1;

        //自定义
        T ans;
        if(jobl<=m){
            ans|=query(jobl,jobr,l,m,i<<1);
        }
        if(m+1<=jobr){
            ans|=query(jobl,jobr,m+1,r,i<<1|1);
        }
        return ans;
    }
};

void solve()
{
    int n,m;
    cin>>n>>m;

    Segment_Tree<Xor_Basis<30>>tree(m+1);

    int op,k,x,l,r;
    while(n--)
    {
        cin>>op;
        if(op==1)
        {
            cin>>k>>x;

            tree.add(k,x,1,m,1);
        }
        else
        {
            cin>>l>>r;

            auto res=tree.query(l,r,1,m,1);

            cout<<res.query_max()<<endl;
        }
    }
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    //cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

线性基其实也是可以用线段树维护的,方法就是在线段数的每个节点都开一个线性基。

那么添加操作就是把从根节点一直到叶子节点路径上的每个节点都加入这个数,范围查询就是考虑设置一个整体的线性基,然后带着这个线性基,每次被全包的时候把这个节点的线性基全加入这个整体的线性基。这样就能收集到整个区间的线性基了,最后再取最大异或和即可。

2.幸运数字

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

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

template<int BIT,typename T=ll>
struct Prefix_Basis{
    int cnt;
    T data[BIT+1];
    int time[BIT+1];

    Prefix_Basis(){reset();}

    void reset(){
        cnt=0;
        for(int i=0;i<=BIT;i++)data[i]=0,time[i]=-1;
    }

    bool insert(T x, int t){
        for(int i=BIT;i>=0&&x;i--){
            if(x>>i&1){
                if(data[i]==0){
                    data[i]=x;
                    time[i]=t;
                    cnt++;
                    return true;
                }
                if(time[i]<t){
                    swap(data[i],x);
                    swap(time[i],t);
                }
                x^=data[i];
            } 
        }
        return false;
    }

    T query_max(T x=0,int l=0){
        for(int i=BIT;i>=0;i--){
            if(time[i]<l){
                continue;
            }

            if(~x>>i&1){
                x^=data[i];
            }
        }
        return x;
    }

    friend constexpr Prefix_Basis operator|(const Prefix_Basis &lhs, const Prefix_Basis &rhs){
        Prefix_Basis res=lhs;
        for(int i=0;i<=BIT;i++){
            res.insert(rhs.data[i],res.time[i]);
        }
        return res;
    }

    constexpr Prefix_Basis& operator|=(const Prefix_Basis &rhs){
        for(int i=0;i<=BIT;i++){
            if(rhs.data[i]){
                insert(rhs.data[i],rhs.time[i]);
            }
        }
        return *this;
    }

    friend constexpr Prefix_Basis operator&(const Prefix_Basis &lhs, const Prefix_Basis &rhs){
        using i128=__int128_t;

        Prefix_Basis<BIT*2,i128>tmp;
        for(int i=0;i<=BIT;i++){
            tmp.insert(((i128)lhs.data[i]<<BIT)|lhs.data[i],lhs.time[i]);
            tmp.insert(rhs.data[i],rhs.time[i]);
        }
        Prefix_Basis res;
        for(int i=0;i<BIT;i++){
            if(tmp.data[i]){
                res.data[i]=tmp.data[i];
                res.time[i]=tmp.time[i];
                res.cnt++;
            }
        }
        return res;
    }
};

struct LCA
{
    int MAXP;
    vector<int>deep;
    vector<vector<int>>stjump;
    //自定义
    vector<Prefix_Basis<60>>bases;

    int log2(int n){
        int ans=0;
        while((1<<ans)<=(n>>1)){
            ans++;
        }
        return ans;
    }

    LCA(int n,const vector<vector<int>>&g,const vector<ll>&a,int s=1){
        MAXP=log2(n);

        deep.assign(n+1,0);
        stjump.assign(n+1,vector<int>(MAXP+1,0));
        //自定义
        bases.assign(n+1,Prefix_Basis<60>());

        dfs(s,0,g,a);
    }

    void dfs(int u,int fa,const vector<vector<int>>&g,const vector<ll>&a){
        deep[u]=deep[fa]+1;
        stjump[u][0]=fa;
        //自定义
        bases[u]=bases[fa];
        bases[u].insert(a[u],deep[u]);
    
        for(int p=1;(1<<p)<=deep[u];p++){
            stjump[u][p]=stjump[stjump[u][p-1]][p-1];
            //自定义
        }
    
        for(auto v:g[u]){
            if(v!=fa){
                dfs(v,u,g,a);
                //自定义
            }
        }
    }

    int query(int a,int b){
        if(deep[a]<deep[b]){
            swap(a,b);
        }
    
        //自定义

        for(int p=MAXP;p>=0;p--){
            if(deep[stjump[a][p]]>=deep[b]){
                a=stjump[a][p];
            }
        }
    
        if(a==b){
            return a;
        }
    
        for(int p=MAXP;p>=0;p--){
            if(stjump[a][p]!=stjump[b][p]){
                a=stjump[a][p];
                b=stjump[b][p];
            }
        }

        return stjump[a][0];
    }

    int length(int a,int b){
        int ac=query(a,b);
        return deep[a]+deep[b]-2*deep[ac];
    }
};

void solve()
{
    int n,q;
    cin>>n>>q;
    vector<ll>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    vector<vector<int>>g(n+1);
    for(int i=1,u,v;i<=n-1;i++)
    {   
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    LCA lca(n+1,g,a);

    int x,y;
    while(q--)
    {
        cin>>x>>y;

        int fa=lca.query(x,y);

        Prefix_Basis<60>res;
        for(int i=60;i>=0;i--)
        {
            ll num=lca.bases[x].data[i];
            int level=lca.bases[x].time[i];
            if(num&&level>=lca.deep[fa])
            {
                res.insert(num,0);
            }
        }
        for(int i=60;i>=0;i--)
        {
            ll num=lca.bases[y].data[i];
            int level=lca.bases[y].time[i];
            if(num&&level>=lca.deep[fa])
            {
                res.insert(num,0);
            }
        }
        cout<<res.query_max()<<endl;
    }
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    //cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

因为每次查询树上一条路径的最大异或和,所以需要考虑在树上每个点建立线性基。首先,在构建时 dfs 往下扎的时候,类似前缀和的形式,要把当前节点的整个线性基传下去。又因为在查 lca 时,需要区分当前节点的某个线性基是否来自这条路径上,所以考虑对于相同的线性基,记录最晚插入的层数。这样在查询 lca 的时候,就可以快速判断当前这条线性基是否能要了。这个就牵扯到线性基的替换保留和更新操作了。

对于当前节点的值,在往线性基里插入时,不管最高位 1 线性基里有没有,都直接往里插入,更新层数。而对于替换出来的值,是有可能更新剩下的线性基的。所以就让其异或上新插入的值,继续往后考察,同样每次根据层数判断是否可以更新之前的即可。

所以在构建好每个节点的前缀线性基后,在查出 lca 后,先把 x 有效的线性基都插入整体线性基,再把 y 有效的都插入整体。注意 x 因为初始没有基所以可以直接抄一遍,但在 y 插入时需要判断当前位的整体线性基是否有值了,有就需要异或往后考虑。

忽然发现好像不需要线性基的板子()

3.最大XOR和路径

经典左神最后一道变态难题……

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

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

template<typename T,int BIT,double eps>
struct Basis
{
public:
    struct Node
    {
        vector<T>a;
        bool zero;
        int len;
    };

    //异或
    bool insert(T x,vector<T>&ans){
        for(int i=BIT;i>=0;i--){
            if(x>>i&1){   
                if(ans[i]==0){
                    ans[i]=x;
                    return true;
                }
                x^=ans[i];
            }
        }
        return false;
    }

    //普通消元解决异或空间线性基
    //返回一组线性基、是否有0、线性基个数
    Node normal_eor(int n,const vector<T>&a){
        vector<T>ans(BIT+1);
        bool zero=false;
        int len=0;
        for(int i=1;i<=n;i++){
            if(!insert(a[i],ans)){
                zero=true;
            }
            else{
                len++;
            }
        }
        return {ans,zero,len};
    }

    //高斯消元解决异或空间线性基 -> 可以求出第k大的异或和
    //返回一组线性基、是否有0、线性基个数
    Node gauss_eor(int n,const vector<T>&a){
        vector<T>ans(n+2);
        for(int i=1;i<=n;i++){
            ans[i]=a[i];
        }

        int len=1;
        for(int i=BIT;i>=0;i--){
            for(int j=len;j<=n;j++){
                if(ans[j]>>i&1){
                    swap(ans[j],ans[len]);
                    break;
                }
            }

            if(ans[len]>>i&1){
                for(int j=1;j<=n;j++){
                    if(j!=len&&(ans[j]>>i&1)){
                        ans[j]^=ans[len];
                    }
                }
                len++;
            }
        }
        len--;
        return {ans,len!=n,len};
    }

    //向量
    bool insert(int i,int m,vector<vector<double>>&mat,vector<double>&ans){
        for(int j=1;j<=m;j++){
            if(abs(mat[i][j])>eps){
                if(ans[j]==0){
                    ans[j]=i;
                    return true;
                }

                double rate=mat[i][j]/mat[ans[j]][j];
                for(int k=j;k<=m;k++){
                    mat[i][k]-=rate*mat[ans[j]][k];
                }
            }
        }
        return false;
    }

    //普通消元解决向量空间线性基
    //返回一组线性基、线性基个数
    pair<vector<double>,int> normal_vec(int n,int m,vector<vector<double>>&mat){
        vector<double>ans(m+1);
        int len=0;
        for(int i=1;i<=n;i++){
            if(insert(i,m,mat,ans)){
                len++;
            }
        }
        return {ans,len};
    }

};

constexpr int BIT=60;
constexpr double eps=1e-7;
Basis<ll,BIT,eps>B;

void solve()
{
    int n,m;
    cin>>n>>m;
    vector<vector<pll>>g(n+1);
    for(int i=1;i<=m;i++)
    {
        ll u,v,w;
        cin>>u>>v>>w;
        g[u].push_back({v,w});
        g[v].push_back({u,w});
    }

    vector<ll>basis(BIT+1);

    vector<ll>path(n+1);
    vector<int>vis(n+1);

    auto dfs=[&](auto &&self,int u,int fa,ll cur)->void
    {
        path[u]=cur;
        vis[u]=1;
        
        for(auto [v,w]:g[u])
        {
            if(v!=fa)
            {
                ll nxt=cur^w;
                if(vis[v])
                {
                    B.insert(nxt^path[v],basis);
                }
                else
                {
                    self(self,v,u,nxt);
                }
            }
        }
    };

    dfs(dfs,1,0,0);

    ll ans=path[n];
    for(int i=BIT;i>=0;i--)
    {
        ans=max(ans,ans^basis[i]);
    }
    cout<<ans<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    //cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

对于难题,可以选择先从简单情况入手,再逐步推广到一般情况。

首先,对于树上图上问题,肯定是从一条链的情况开始考虑。那么此时由于路径是唯一的,那么答案就是从 1 走到 n 的异或和。之后,再考虑是一棵树的情况,那么由于树上两点之前的路径也是唯一的,而且根据异或运算的性质,如果走往路径外走再走回来,那么异或和就消掉了,所以答案也是从 1 走到 n 的异或和。

之后考虑有环的情况,首先考虑在从 1 到 n 的路径上存在一个由一个点延伸出去的环。此时也很简单,就是可以选择走环还是不走环。如果走环的话,因为必然需要走完整个环,那么就是原本一条链的路径异或上这个环的异或和。而当有多个这样的环时,因为每个都可以选择绕或不绕,那么若以每个环的异或和为单位,其实就是在初始链路的基础上,所有环能组成的最大异或和,这个就可以通过把每个环的异或和插入线性基里来解决。

而当存在多条链路时,最终计算出的 path[n] 只是其中一个,此时依然是能保证正确的。假如要选择一条和这条链路不直接相连的环,那么其实就只需要走到 n 后再从 n 出发走另外的路去绕一遍环再回来即可,多余的路因为走了两遍所以就被消掉了。而如果正确的链路不是 path[n],那么此时 path[n] 肯定可以和正确的链路形成一个环,那么就同样可以通过异或这个大环来把另一条链路加入并且把 path[n] 消掉。

所以在实现时,就只需要用 vis 表记录是否来过。若要去往的点 v 之前来过,那么就说明找到了一个环,此时整个环的异或值就是 path[v]^w^path[u]。那么把这个异或值插入线性基,然后直接返回即可,这样就能保证 path[n] 是不包含环的链路。

总结

感觉这个对解决异或和问题很有帮助啊!

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值