线性基

什么是线性基

在线性代数里面我们其实已经学过线性基了,类似极大线性无关组和空间的基,一组线性无关的向量,可以构建一个向量空间,这组线性无关的向量就构成了这个空间的一个基地,这个基地就是线性基。

  • 线性基的意义

线性基是一种特殊的基,它通常会在异或运算中出现,它的意义是:假设若干数的线性基是一组数a1,a2,...an,其中ax的最高位的1在第x位。并且通过原集合S的某一个最小子集S1使得S1内元素相互异或得到的值域与原集合S相互异或得到的值域相同。

  • 线性基的性质
  1. 线性基能相互异或得到原集合的所有相互异或得到的值。

  2. 线性基是满足性质1的最小的集合

  3. 线性基没有异或和为0的子集

  • 线性基的插入与删除

我们考虑插入的操作,令插入的数为 x ,考虑 x 的二进制最高位 i ,

  • 若线性基的第 i 位为 0 ,则直接在该位插入 x ,退出;
  • 若线性基的第 i 位已经有值 ai​ ,则 x =x⊕ai​ ,重复以上操作直到 x=0。

如果退出时 x=0 ,则此时线性基已经可以表示原先的 x 了;反之,则说明为了表示 x ,往线性基中加入了一个新元素。

代码:

void ins(ll x)
{
    for(reg 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(reg int i=MN; ~i; i--)
        if(x&(1ll<<i))
            if(!a[i])
                return false;
            else
                x^=a[i];
    return true;
}
  • 查询异或最值

查询最小值相对比较简单。考虑插入的过程,因为每一次跳转操作, x 的二进制最高位必定单调降低,所以不可能插入两个二进制最高位相同的数。而此时,线性基中最小值异或上其他数,必定会增大。所以,直接输出线性基中的最小值即可。

考虑异或最大值,从高到低遍历线性基,考虑到第 i 位时,如果当前的答案 x 第 i 位为 0 ,就将 x 异或上 ai​ ;否则不做任何操作。显然,每次操作后答案不会变劣,最终的 x 即为答案。

同样,我们考虑对于一个数 x ,它与原数列中的数异或的最值如何获得。用与序列异或最大值类似的贪心即可解决。

  • 查询k小值

​​​​​​​我们考虑进一步简化线性基。显然,一个线性基肯定可以表示为若干个形如 2^i 的数。从高到低处理线性基每一位,对于每一位向后扫,如果当前数第 i 位为 0 ,且线性基第 i 位不为 0 ,则将当前数异或上 ai​ 。这一操作可以在 O(n^2)的时间内解决。

经过这一步操作后,设线性基内共有 cnt 个数,则它们共可以表示出 2^cnt 个数。当然,对于 0 必须特殊考虑。如果插入的总数 n 与 cnt 相等,就无法表示 0 了。

同样,考虑最小值时,也必须要考虑到 0 的情况。事实上,如果插入时出现了未被加入的元素,就肯定可以表示出 0。

随后,我们考虑将 k 二进制拆分,用与快速幂类似的方法就可以求出第 k 大值。

学过线性代数的同学应该可以看出,这个过程就是对一个矩阵求解异或意义下的秩的过程。因此,cnt<=[log(N)/log(2)]显然成立,而最终,线性基中保存的也是异或意义下的一组极小线性无关组。

同样,有关线性基的一切运算都可以看做矩阵的初等行列变换,也就可以将其看做线性规划问题。同样,可以离线使用高斯消元来构造极小线性基。

代码:

bool flag;//可以表示0
ll qmax(ll res=0)
{
    for(reg int i=MN; ~i; i--)
        res=max(res,res^a[i]);
    return res;
}
ll qmin(ll res=0)
{
    if(flag)
        return 0;
    for(reg int i=0; i<=MN; i++)
        if(a[i])
            return a[i];
}
ll query(ll k)
{
    reg ll res=0;
    reg int cnt=0;
    k-=flag;
    if(!k)
        return 0;
    for(reg 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(reg int i=0; i<cnt; i++)
        if(k&(1ll<<i))
            res^=tmp[i];
    return res;
}

​​​​​​​线性基板子:

#include<bits/stdc++.h>
#define reg register
using namespace std;
typedef long long ll;
const int MN=60;
ll a[61],tmp[61];
bool flag;
void ins(ll x)
{
    for(reg 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(reg 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(reg int i=MN; ~i; i--)
        res=max(res,res^a[i]);
    return res;
}
ll qmin()
{
    if(flag)
        return 0;
    for(reg int i=0; i<=MN; i++)
        if(a[i])
            return a[i];
}
ll query(ll k)
{
    reg ll res=0;
    reg int cnt=0;
    k-=flag;
    if(!k)
        return 0;
    for(reg 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(reg 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;
}
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值