线性代数 —— 线性基与前缀线性基

【概述】

线性基,是线性代数中的概念,在信息学竞赛中,前缀线性基是线性基的扩展,他们主要用于处理有关异或和的极值问题

一组线性无关的向量即可作为一组基底,张起一个线性的向量空间,这个基底即称为线性基,利用线性基的基底进行线性运算,可表示向量空间内的所有向量,换句话说,所有向量都可以拆成基底的线性组合。

根据异或的原理,将一个数字拆成他的二进制形式,将二进制形式用向量来表示,由于一组线性无关的向量可以张起一个向量空间,因此可以考虑构造这样一组数字的二进制形式组成的线性基,在这个线性基中,通过基底的线性组合、异或运算,从而可以表达所有的异或结果。

简单来说,若一个数集 T 的值域范围为 [1,2^{n-1}],那么 T 的线性基是 T 的一个子集 A={a1,a2,a3,...,an},A 中元素相互异或而成的集合,等价于原数集 T 的元素相互异或形成异或集合

【线性基】

1.构造

线性基具有以下几个性质:

  • 原数列中的任意一个数,均可由线性基中的一些数异或得到
  • 线性基中的数的个数唯一,且在保证唯一性的前提下,数的个数是最少的
  • 线性基的异或集合中,每个元素的异或方案唯一 ,且异或集合中不存在 0
  • 线性基中元素互相异或,异或集合不变
  • 线性基二进制最高位互不相同

1)插入

由于 xor 偶数具有抵消性,因此可利用这个性质对加入的向量进行修改。

对于每一个数来说,需要判断其是否能加入到线性基中,将新加入的数字 x 拆为二进制形式,从最高位向最低位扫描其为 1 的二进制位,扫描到第 i 位时,如果线性基向量组 d[i] 中不存在,即 d[i]=0 时,就令 d[i]=x,然后 break;反之,若线性基向量组 d[i] 中存在,即 d[i]=1 时,就令 x=x xor d[i]

这样在扫描完成后,新加入的数字 x,要么被添加进线性基,要么经过一系列操作后变成了 0,如果变成了 0,说明这个数字 x 对应的二进制向量不属于极大线性无关组。

LL d[60+5];//线性基
void add(LL x) {
    for(int i=60; i>=0; i--) {
        if(x&(1LL<<i)) {
            if(d[i])//插入失败,异或
                x^=d[i];
            else {//插入成功,保存后退出
                d[i]=x;
                break;
            }
        }
    }
}

2)合并

当两个线性基需要合并时,直接将一个线性基暴力的插入另一个线性基即可。

LL d1[60+5],d2[60+5];//合并前的两个线性基
LL d[60+5];//合并后的线性基
void Union(){
    memcpy(d,d1,sizeof(d1));
    for(int i=0;i<=60;i++){
        if(d2[i])
            add(d2[i]);
    }
}

2.查询

在线性基构造完毕后,即可对线性基进行查询。

1)最大值

当要查询最大值时,就从高位向低位扫描线性基,如果异或后使得结果变大,就异或进答案中。

LL d[60+5];//线性基
LL queryMax() {
    LL res=0;
    for(int i=60; i>=0; i--)
        if((res^d[i])>res)
            res^=d[i];
    return res;
}

2)最小值

当要查询最小值时,最低位上的线性基即为答案。

LL d[60+5];//线性基
LL queryMin(){
    for(int i=0; i<=60; i++)
        if(d[i])
            return d[i];
    return 0;
}

3)第 k 小值

由于线性基二进制最高位互不相同,因此要将线性基改造为每一位相互独立。

从最高位向最低位枚举,如果 i<j,那么 d[j] 的第 i 位是 1,就将 a[j] 异或上 a[i],经过一系列操作后,对于二进制的某一位 i,只有 a[i] 这一位是 1,其余的都是 0

这样在查询的时候,将 k 进行二进制拆分,对于为 1 的位,就异或上对应的线性基,最终得到的就是第 k 小的值

注:如果要求第 k 大的值,也就是求第 n-k+1 小的值,将 queryKth() 的参数 k 换为 n-k+1 即可

LL d[60+5];//线性基
LL p[60+5];//改造后的线性基
int cnt;
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];
}
LL queryKth(LL k){//查询第k小的值,若要查k大,将k换为n-k+1即可
    int res=0;
    if(k>=(1LL<<cnt))
        return -1;
    for(int i=60; i>=0; i--)
        if(k&(1LL<<i))
            res^=p[i];
    return res;
}

【前缀线性基】

线性基用于处理整个区间异或的极值问题,前缀线性基用来维护整个区间中的一段区间异或的极值问题。

1.构造

前缀线性基建立在线性基的基础上,其是对于 i 都建立一个 1~i  数组成的线性基。

假设有 4 个数 1、2、3、4,那么就建立 4 个基底,分别为:

  • 第一基底:1
  • 第二基底:1、2
  • 第三基底:1、2、3
  • 第四基底:1、2、3、4

在构造 i 个基底时,先将前 i-1 个 基复制过来,然后再加上第 i 个数,来构造一个新的基底。

如果对于基底的某个位置上的数有能更新的数,就进行替换,以保证对于某个右端点 R 能尽可能的用靠近 R 的数作为基,也就保证了所有 [L,R] 中的数都尽可能的参与构造这个数。

例如:对于第五个基的第 3 个位置,如果第 4 个数和第 2 个数都能放在这个位置,那么就放第 4 个数,因为查询区间 [3,5] 时能用 3、4、5 构造这个数,包含了 4;查询区间 [2,5] 时,能用 2、3、4、5 构造这个数

也就是说对于 [2,5] 来说,第 2 个数还是第 4 个数都可以使用,但是 [3,5] 来说,只能使用第 4 个数,因此当第 4 个数和第 2 个数都能放在第 3 个位置时,优先用第 4 个数

void add(int x) { //向线性基中添加x
    num++;
    for(int i=0; i<32; i++) { //复制前num-1个线性基
        d[num][i]=d[num-1][i];
        pos[num][i]=pos[num-1][i];
    }

    int P=num;
    for(int i=31; i>=0; i--) {
        if((x>>i)&1) {
            if(d[num][i]) { //插入失败
                if(pos[num][i]<P) { //交换位置
                    swap(pos[num][i], P);
                    swap(d[num][i],x);
                }
                x^=d[num][i];//异或
            } else { //插入成功
                d[num][i]=x;
                pos[num][i]=P;
                break;
            }
        }
    }
}

2.查询

在查询 [L,R] 区间时,就找到第 R 个基,然后用 pos[i] 来记录第 i 个位置是哪个数,如果 pos<l,那么就说明不是区间 [L,R] 的基

1)最大值

int queryMax(int l,int r) { //查询[l,r]中的最大值
    int res=0;
    for (int i=31; i>=0; i--) {
        if(pos[r][i]<l)
            continue;
        if ((res^d[r][i])>res)
            res^=d[r][i];
    }
    return res;
}

2)最小值

int queryMin(int l,int r) {//查询[l,r]中的最小值
    for(int i=0; i<=60; i++) {
        if(pos[r][i]<l)
            continue;
        if(d[r][i])
            return d[r][i];
    }
    return 0;
}

【模版】

1.线性基

struct LinearBasis {
    int num;//线性基中元素个数
    LL d[60+5];//线性基
    LL p[60+5];//查询第k小时的改造的线性基
    int cnt;//改造线性基用
    LinearBasis() {
        memset(d,0,sizeof(d));
        memset(p,0,sizeof(p));
        num=0;
        cnt=0;
    }
    bool add(LL x){
        for(int i=60; i>=0; i--) {
            if(x&(1LL<<i)) {
                if(d[i])//插入失败,异或
                    x^=d[i];
                else {//插入成功,保存后退出
                    num++;//统计线性基中元素个数
                    d[i]=x;
                    break;
                }
            }
        }
        return x>0;//x>0插入成功
    }
    LL queryMax() {//查询最大值
        LL res=0;
        for(int i=60; i>=0; i--)
            if((res^d[i])>res)
                res^=d[i];
        return res;
    }
    LL queryMin() {//查询最小值
        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];
    }
    LL queryKth(LL k) {//查询第k小的值,若要查k大,将k换为n-k+1即可
        int res=0;
        if (k>=(1LL<<cnt))
            return -1;
        for (int i=60; i>=0; i--)
            if (k&(1LL<<i))
                res^=p[i];
        return res;
    }
};
LinearBasis Union(const LinearBasis &rhs1,const LinearBasis &rhs2) {
    LinearBasis res=rhs1;
    for(int i=60; i>=0; i--)
        if(rhs2.d[i])
            res.add(rhs1.d[i]);
    return res;
}

2.前缀线性基 

struct PrefixLinearBasis{
    int d[N][32];//前缀线性基
    int pos[N][32];//最后一个修改i这个位置的数
    int num;//线性基中元素个数
    PrefixLinearBasis(){
        memset(d,0,sizeof(d));
        memset(pos,0,sizeof(pos));
        num=0;
    }
    void clear(){
        memset(d,0,sizeof(d));
        memset(pos,0,sizeof(pos));
        num=0;
    }
    void add(int x){//向线性基中添加x
        num++;
        for(int i=0; i<32; i++){//复制前num-1个线性基
            d[num][i]=d[num-1][i];
            pos[num][i]=pos[num-1][i];
        }

        int P=num;
        for(int i=31; i>=0; i--){
            if((x>>i)&1){
                if(d[num][i]){//插入失败
                    if(pos[num][i]<P){//交换位置
                        swap(pos[num][i], P);
                        swap(d[num][i],x);
                    }
                    x^=d[num][i];//异或
                }
                else{//插入成功
                    d[num][i]=x;
                    pos[num][i]=P;
                    break;
                }
            }
        }
    }
    int queryMax(int l,int r){//查询[l,r]中的最大值
        int res=0;
        for (int i=31; i>=0; i--){
            if(pos[r][i]<l) 
                continue;
            if ((res^d[r][i])>res) 
                res^=d[r][i];
        }
        return res;
    }
    int queryMin(int l,int r) {//查询[l,r]中的最小值
        for(int i=0; i<=60; i++){
            if(pos[r][i]<l)
                continue;
            if(d[r][i])
                return d[r][i];
        }
        return 0;
    }
}PLB;

【例题】

posted @ 2022-09-20 22:51  老程序员111  阅读(158)  评论(0编辑  收藏  举报