线性基

鸣谢Yveh博主的博客

一、定义

设数集T的值域范围为[1,2n-1] ,

T的线性基是T的一个子集A={a1,a2,a3,…,an}。

A中元素互相xor所形成的异或集合,等价于原数集T的元素互相xor形成的异或集合。

(可以理解为将原数集进行了压缩)

例:A={1,2,4}可以是数集T=[1,7]的一个线性基,因为A中数的异或和可以表示T中任意的一个数。

我们如果把上例的十进制数拆成二进制数就能知道,这与线代中“一组线性无关的向量可张起一个向量空间”是一个道理。
A = [ 1 0 0 0 1 0 0 0 1 ] A=\left[ \begin{matrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \\ \end{matrix} \right] A=100010001

当然,如果选取A={1,3,5}亦可……此时:
A = [ 1 0 1 0 1 1 0 0 1 ] A=\left[ \begin{matrix} 1 & 0 & 1 \\ 0 & 1 & 1 \\ 0 & 0 & 1 \\ \end{matrix} \right] A=100010111
(A经过初等行变换获得的最简的基底就是**A={ai|ai=2i,i=0,1,2……}**这组基底)

二、一些性质

1. 线性基的异或集合中不存在0。

  • 就是不能从线性基内取出一些数,使得它们的异或和为0(十分显然,对吧)

2. 线性基的异或集合中每个元素的异或方案唯一,其实这个跟性质1是等价的。

  • 大概就是说一个数只能由某几个数的异或和得到,并且找不到第二种异或和等于这个数的方案

3. 线性基二进制最高位互不相同。

  • 只有ai位最高位为1,其它的a向量最高位都不能是第i位(稍后的插入元素会有代码过程)

4. 如果线性基是满的,它的异或集合为[1,2n−1]。

  • 很显然

5. 线性基中元素互相异或,异或集合不变。

  • 显然++

三、插入元素到线性基中

1.如果向线性基中插入数x,从高位到低位扫描它为1的二进制位。
2.扫描到第i时,如果ai不存在,就令ai=x,否则x=x⊗ai。
3.x的结局是,要么被扔进线性基,要么经过一系列操作过后,变成了0。
(插入元素n的复杂度为O(logn))

bool insert(long long val){
    for (int i=60;i>=0;i--)
        if (val&(1LL<<i)){//扫描为1的二进制位
            if (!a[i]){//如果不存在就把val放进线性基中
                a[i]=val;
                break;
            }
            val^=a[i];//存在的话就异或一下接着找
        }
    return val>0;//返回1即插入成功
}

四、暴力合并两个线性基

L_B merge(const L_B &n1,const L_B &n2){
    L_B ret=n1;
    for (int i=0;i<=60;i++)
        if (n2.d[i])
            ret.insert(n2.d[i]);
    return ret;
}

五、一些具体应用

1.存在性问题

如果要查询x是否存于异或集合中,从高位到低位扫描x的为1的二进制位,且扫描到第i位的时候x=x⊗ai,如果在查询中途x变为了0,那么表示x存于线性基的异或集合中。

2.求异或集合中的最大值

很简单,从高位到低位扫描线性基,如果异或后可以使得答案变大,就异或到答案中去。

long long query_max(){
    long long ret=0;
    for (int i=60;i>=0;i--)
        if ((ret^d[i])>ret)
            ret^=d[i];
    return ret;
}
3.求异或集合中的最小值
long long query_min(){
    for (int i=0;i<=60;i++)
        if (d[i])
            return d[i];
    return 0;
}

……

六、模板(再次感谢Yveh博主!)

struct L_B{
    long long d[61],p[61];
    int cnt;
    L_B(){
        memset(d,0,sizeof(d));
        memset(p,0,sizeof(p));
        cnt=0;
    }
    bool insert(long long val){
        for (int i=60;i>=0;i--)
            if (val&(1LL<<i)){
                if (!d[i]){
                    d[i]=val;
                    break;
                }
                val^=d[i];
            }
        return val>0;
    }
    long long query_max(){
        long long ret=0;
        for (int i=60;i>=0;i--)
            if ((ret^d[i])>ret)
                ret^=d[i];
        return ret;
    }
    long long query_min(){
        for (int i=0;i<=60;i++)
            if (d[i]) return d[i];
        return 0;
    }
    void rebuild(){//利用了高斯消元
        for (int i=60;i>=0;i--)
            for (int j=i-1;j>=0;j--)
                if (d[i]&(1LL<<j))
                    d[i]^=d[j];
        for (int i=0;i<=60;i++)
            if (d[i]) p[cnt++]=d[i];
    }
    long long kthquery(long long k){//求最小值
        int ret=0;
        if (k>=(1LL<<cnt))
            return -1;
        for (int i=60;i>=0;i--)
            if (k&(1LL<<i))
                ret^=p[i];
        return ret;
    }
}
L_B merge(const L_B &n1,const L_B &n2){
    L_B ret=n1;
    for (int i=60;i>=0;i--)
        if (n2.d[i]) ret.insert(n1.d[i]);
    return ret;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值