【模板】线性基

本文详细介绍了线性基的概念及其在ACM竞赛中的应用,包括线性基的构造、插入操作以及常见模板问题的解决方法,如求异或最大值、最小值、元素表示和第k小值等。内容覆盖了线性基的基本操作和优化技巧,并给出了相关练习题的解题思路。

ACM模板


学习线性基可考虑以下大佬博客
知乎Pecco博客
博客园Kaori博客
menci博客
肖然博客
从线性代数谈线性基(有点硬核)

构造线性基

普通插入:
不能保证除了主元上其他线性基元素该位置为1

typedef unsigned long long ull;
ull p[64];
void insert(ull x)
{
    for(int i=63;i>=0;i--)
    {
        if(!(x>>i&1)) continue;
        if(!p[i]) 
        {
            p[i]=x;
            break;
        }
        x^=p[i];
    }
}

进阶插入:

若p[i]!=0(即主元i存在),则线性基中只有p[i]的第i位是1;且此时p[i]的最高位就是第i位

bool insert(ll x)
{
    for(int i=62;i>=0;i--)
    {
        if(!(x>>i&1)) continue;
        if(p[i])
        {
            x^=p[i];
            continue;
        }
        for(int j=i-1;j>=0;j--)
            if(x>>j&1) x^=p[j];
        for(int j=62;j>i;j--)
            if(p[j]>>i&1) p[j]^=x;
        p[i]=x;
        return 1;
    }
    return 0;
}

线性基模板操作

1.在一系列数中选若干数做异或,求最大值。

反向遍历p[]数组,按高位贪心(因为对于每个p[i]来说,当且仅当目前的res的第i位为0时,选这个数可以使res增大。而此时,选它一定比不选它更优)

普通插入代码:

ull getmax()
{
    ull res=0;
    for(int i=63;i>=0;i--)
        if((res^p[i])>res) res^=p[i];
    return res;
}

进阶插入:直接把所有p[i]异或即是最大值。

2.在一系列数中选若干数做异或,求最小值。

直接求线性基,然后选最小的一个即可。显然它与线性基中任意其他数异或后,都不会更小。
注意:特判0

普通插入代码:

ull getmin()
{
	if(cnt0) return 0;
    for(int i=0;i<=63;i++)
        if(p[i]) 
            return p[i];
}

进阶插入:找到最小主元即可

3.查询某数是否能被表示出来

按照插入的思路进行检查即可。

bool belong(ull x)
{
    for(int i=63;i>=0;i--)
    {
        if(!(x>>i&1)) continue;
        if(!p[i]) return false;
        x^=p[i];
    }
    return true;
}

4.查询第k小值

使用进阶插入解决此问题,把k进行二进制分解,把1对应位置的主元xor起来。
注意:第0小是0

ll now[64];
int cnt=0;
for(int i=0;i<=62;i++)
	if(p[i]) now[cnt++]=p[i];
ll query(ll k)
{
    if(flag) k--;
    if(!k) return 0;
    if(k>=(1ull<<cnt)) return -1;
    ll res=0;
    for(int i=0;i<cnt;i++)
        if(k>>i&1) res^=now[i];
    return res;
}

线性基相关题目

【模板】线性基
普通插入即可解决

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=70;
ull p[N];
int n;
void insert(ull x)
{
    for(int i=63;i>=0;i--)
    {
        if(!(x>>i&1)) continue;
        if(!p[i]) 
        {
            p[i]=x;
            break;
        }
        x^=p[i];
    }
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) 
    {
        ull x;
        cin>>x;
        insert(x);
    }
    ull res=0;
    for(int i=63;i>=0;i--)
        if((res^p[i])>res) res^=p[i];//不难发现只有(res>>i&1)==0是才会满足if语句
    cout<<res<<'\n';
    return 0;
}

异或运算
第k异或小,使用进阶插入即可解决。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=10010;
ll p[70];
int n,q;
bool flag;
ll now[70];
int cnt;
bool insert(ll x)
{
    for(int i=62;i>=0;i--)
    {
        if(!(x>>i&1)) continue;
        if(p[i])
        {
            x^=p[i];
            continue;
        }
        for(int j=i-1;j>=0;j--)
            if(x>>j&1) x^=p[j];
        for(int j=62;j>i;j--)
            if(p[j]>>i&1) p[j]^=x;
        p[i]=x;
        return 1;
    }
    return 0;
}
ll query(ll k)
{
    if(flag) k--;
    if(!k) return 0;
    if(k>=(1ull<<cnt)) return -1;
    ll res=0;
    for(int i=0;i<cnt;i++)
        if(k>>i&1) res^=now[i];
    return res;
}
int main()
{
    int T;
    cin>>T;
    for(int ca=1;ca<=T;ca++)
    {
        printf("Case #%d:\n",ca);
        memset(p,0,sizeof p);
        flag=0;cnt=0;
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            ll x;
            cin>>x;
            if(!insert(x)) flag=1;
        }
        cin>>q;
        for(int i=0;i<=62;i++)
            if(p[i]) now[cnt++]=p[i];
        while(q--)
        {
            ll k;
            cin>>k;
            cout<<query(k)<<'\n';
        }
    }
    return 0;
}

[BJWC2011]元素
按照magic逆序,然后线性基插入

#define IO ios::sync_with_stdio(false);cin.tie();cout.tie(0)
#include<set>
#include<map>
#include<cmath>
#include<queue>
#include<string>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef unsigned long long ull;
const int N=1010;
ull p[N];
struct node
{
    ll id,w;
    bool operator<(const node& o) const 
    {
        return w>o.w;
    }
}q[N];
int n;
bool insert(ull x)
{
    for(int i=63;i>=0;i--)
    {
        if(!(x>>i&1)) continue;
        if(!p[i]) return p[i]=x,1;
        x^=p[i];
    }
    return 0;
}
int main()
{
    IO;
    int T=1;
    //cin>>T;
    while(T--)
    {
        cin>>n;
        for(int i=1;i<=n;i++) cin>>q[i].id>>q[i].w;
        sort(q+1,q+1+n);
        ll res=0;
        for(int i=1;i<=n;i++)
        {
            if(insert(q[i].id)) 
                res+=q[i].w;
        }
        cout<<res<<'\n';
    }
    return 0;
    
}

Good subset
线性基+线段树题解
搜索题解
2020/09/30 要加油哦~

线性基可以用来判断原集合是否封闭。如果一个元素能够被线性基的基向量线性表示,那么它就可以由原集合中的元素经过线性组合得到,即原集合是封闭的。否则,如果有一个元素不能被线性基的基向量线性表示,那么它就无法由原集合中的元素经过线性组合得到,即原集合不是封闭的。 具体地,我们可以通过将待判断的元素与线性基的基向量进行异或操作来判断是否能够线性表示。如果待判断元素与线性基的基向量进行异或操作后得到零向量,则说明待判断元素可以由线性基的基向量线性表示。如果待判断元素与线性基的基向量进行异或操作后得到非零向量,则说明待判断元素无法由线性基的基向量线性表示。 因此,我们可以通过判断待判断元素与线性基的基向量进行异或操作的结果是否为零向量来判断原集合是否封闭。如果待判断元素与线性基的基向量进行异或操作后都得到零向量,则原集合是封闭的;否则,原集合不是封闭的。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [线性基模板](https://blog.csdn.net/weixin_43519854/article/details/96977900)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [【矩阵论】线性空间与线性变换(3)(4)](https://blog.csdn.net/kodoshinichi/article/details/108916238)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值