学习:数学----线性基

线性基主要解决关于一些数的异或等问题,其中包括解决一堆数中任意几个数异或的最大值,最小值,第k大值等等。

线性基介绍及特点


前言

线性基对于萌新来说刚开始学肯定有点难度的,网上很多博客都把线性基讲复杂了(一开始就讲什么线性无关,什么张成),虽然学过线性代数再来理解线性基的确很容易,但是没学过线性代数而来学习线性基却也没有很多很难得地方(至少你知道异或吧)

所以写这篇博客直接从线性基的特点和作用来讲解,进而来讲其他操作,不会非常涉及到线性代数的某些专业知识。

线性基的组成

当我们有了一组确切的数,我们才能找出这一组数的线性基。故线性基也是一组数,但是注意一点,线性基内可能包含原数组内的数可能也有新的数,这一点在线性基的构造与插入中可以看出。

对于某个确切的数组 $a$ 内的任何一个数 $a[i]$,我们总能在数组 $a$ 的线性基中找到某几个数,使得这某几个数异或起来等于 $a[i]$。你可以把一组数的线性基理解成这一组数的代表数组,也可以理解成这一组数的缩影

线性基的性质

一组数的线性基内有多少数是固定的取决于原数组是int类型还是long long类型,如果是int类型,线性基内的数不多余32个,如果是long long类型,线性基内的数不多余64个。

解释:将原数组的每一个数都化为二进制形式,可能一些数的某一个二进制位是1,也有可能是0。而线性基为了表示整个数组,所以线性基中的每一个数都对于原数组数的二进制某个一位值有贡献(如果原数组的数的某一个二进制位都是0,那么线性基内所有的数的这个二进制位也一定是0,这是一种贡献为0的‘贡献’),而int类型(long long类型)二进制位不超过32(64),故线性基内的数的个数不超过32(64)个。

由于线性基内每一个数主要对原数组所有数的二进制的某一位有贡献,故线性基内不存在一些数异或起来等于0

总结:

1:一组数的线性基是不唯一的,但是线性基内数的数量是惟一且最少的。

2:线性基内不存在一些数异或起来等于0。

3:线性基内的元素某几个元素相互异或能异或出原数组内的值已经原数组内相互异或的值(0除外)。


线性基的构造与插入


线性基的构造

前面说的,线性基内的每一个数有且只对原数组所有数的二进制位的某一位有贡献。

首先创建一个大小为60的数组 $base$ 来存原数组 $origin$ 的线性基(我们默认原数组的数都是long long类型),刚开始 $base$ 内的数都为0

然后我们遍历原数组 $origin$ ,假设我们此时遍历到了$origin[i]$,为了方便,设 $x=origin[i]$

判断x能不能插入线性基了?我们从x的二进制高位往低位遍历(即从第60位往第1位遍历):

如果此时遍历到第 $i+1$ 位,首先判断 x 二进制的第 $i+1$ 位是否为1,即判断 $\text{x&(1ll<<i)}$ 是否等于1($\text{1ll}$ 表示long long类型的数字1)

1.如果不等于1,继续遍历第 $i$ 位;

2.如果等于1,我们判断线性基数组 $base[i]$ 是否等于0,等于0说明线性基此位置之前还没有数插入过,我们就将x插入到 $base[i]$ 中,即令 $base[i]=x$,表示 x 对原数组所有数的二进制第 $i$ 为有贡献;如果 $base[i]$ 处的值不等于0,说明之前已经有数插入到了这个位置,表示有其他说已经对此位做出了贡献,那么x就不需要再做出贡献了我们就让 $x=x\land base[i]$ 来消除x对二进制第 $i$ 位的贡献,然后对于新的x,我们继续遍历第 $i-1$ 位,看看这个位置能不能插入新的x。

伪代码如下:

//***对于x
for(i = 60; i >= 0; i--){
    if(x的二进制第i+1位为1成立){
        if(base[i]不等于0){
            x 异或 base[i]
        }
        else{
            x 插入到 base[i] 中
            跳出循环
        }
    }
}
//****

对一个长度为 $n$ 的数组 $origin$ 构建线性基 $base$ 的代码:

for(int i = 1; i <= n; i++){
    int x = origin[i];
    for(int j = 60; j >= 0; j--){
        if(x & (1ll << j)){
            if(base[j])    x ^= base[j];
            else{
                base[j] = x;
                break;
            }
        }
    }
} 

将原数组的每一个元素尝试插入线性基,就得到原数组的线性基。

线性基的插入

如果你弄懂了线性基的构造,那么你对线性基的插入肯定也是很清楚地。如果你没弄懂线性基的构造,你可以在学习线性基的插入中慢慢领悟线性基的构造。

线性基的构造与线性基的插入是有区别的:如果我们通过线性基的构造来得到了一组数的线性基,那么如果在原数组中插入了一个数,那么此时原数组的线性基是可能会改变了,这就涉及到把一个单个的数尝试插入原有线性基中,也就是线性基的插入。

通过线性基的构造的学习,对于线性基的插入,我们先来讨论一个数能不能插入线性基

1.如果一个数 $x$ 能插入线性基 $base$:

  表示 $x$ 可以有新的贡献,也就是用线性基内的任意多个数都不能异或出 $x$

2.如果一个数 $x$ 能插入线性基 $base$:

  表示在线性基内有若干个数,使得他们的异或和等于x,此时的x完全没有贡献,这也体现了为什么线性基内任何几个元素异或都不会为0(任意多个元素异或出x,x再异或x等于0)

以上所述的 $x$ 的贡献:只要 $x$ 对于原数组的数的二进制位任何位有贡献,都可以认为 $x$ 有贡献,从下图可以看出

上图可以看出,要想把x插入到线性基base中,假设线性基此时只有 $base[0],base[2],base[3]$ 不为0,而且x的二进制高位都为0,所以从我们从 $base[3]$来考虑

很明显,x二进制的第 1<<3 的位置是1,当 $x$ 尝试插入 $base[3]$ 的位置时,发现 $x$ 已经有了一个值13,那么x肯定不能插入到 $base[3]$ 的位置,那么就让 $x$ 异或

$base[3]$,使得 $x=15\land 9=(00110)_{(2)}=6$。

同理在插入 $base[2]$ 的时候,需要异或 $base[2]$ ,此时 $x=6\land 5=(011)_{(2)}=3$

当 $x$ 在尝试插入 $base[1]$ 的时候,成功插入这个位置,于是 $base[1]=x=3$

于是,线性基的构造可以看做任意个线性基的插入,代码如下:

void insert(int x){//线性基插入
    for(int i = 60; i >= 0; i--){
        if(x & (1ll << i)){
            if(base[i])    x ^= base[i];
            else{
                base[i] = x;
                break;
            }
        }
    }
    return;
} 
for(int i = 1; i <= n; i++){//线性基构造
    insert(origin[i]);
} 

甚至可以知道 $origin[i]$ 是否成功的插入了线性基,代码如下:

bool insert(int x){//线性基插入
    for(int i = 60; i >= 0; i--){
        if(x & (1ll << i)){
            if(base[i])    x ^= base[i];
            else{
                base[i] = x;
                return true;
                break;
            }
        }
    }
    return false;
} 

求原数组异或和最大值



由于线性基是原数组的代表,求原数组的异或的最大值,相当于求线性基异或的最大值。

用线性基求异或的最大值的优点原数组的数的个数可能有很多个,如果暴力求的话在实际情况下可能会超时;而线性基内数的个数最多不超过60个,就算是暴力求也可以在很短的时间内求出。

那么如何个暴力法:当然是遍历线性基内每一个数,假设当前算得的异或最大值为 $ans$ ,此时遍历到 $base[i]$ ,如果 $ans\land base[i]>ans$,说明异或上 $base[i]$ 之后,ans可以变大,那么ans的值就可以更新 $ans\land base[i]$

下面逻辑代码:

初始ans = 0;
for(遍历线性基内每一个元素base[i]){
        if(ans异或上base[i]后变大){
        ans异或上base[i];
    }
}
    return ans;

代码:

typedef long long ll;
ll getMaxXor(){
    ll ans = 0;
    for(int i = 0; i <= 60; i++){
        if((ans ^ base[i]) > ans){
            ans ^= base[i];
        }
    }
    return ans;
}

求原数组异或和最小值



结论线性基中最小且不大于0的那个数,就是原数组异或和的最小值

证明:线性基最小的那个数异或上其他数之后,一定会使得异或和变大,所以线性基最小值就是异或和最小值。

方法:只需从base[0]遍历到base[60],一旦有某一个数大于0,就是异或和最小值。

代码:

ll getMinXor(){
    for(int i = 0; i <= 60; i++){
        if(base[i] > 0){
            return base[i];
        }
    }
}

求原数组异或和第k小



由于线性基不唯一,所以我们直接用当前线性基来求第k小时不太可能的(也是没有方法的),为了让线性基变得唯一,我们需要将线性基改造,如下图所示(下图是二进制表示形式)

如上图,先把每一个数拆成二进制,写成一行,就成了一个矩阵。每一行的首非0元素对应的一列中还有其他非0元素,则将两行对应十进制中较大那个数异或较小那个数使得较大那个数的二进制表示在这一列为0。如此,使得这一列的其他元素都为0为止,为了方便,可以创建一个变量 $cnt$ 来存线性基中非0元素的个数(比如上图中非0元素个数为5,第二行为0)

int build(){
    int cnt = 0;
    for(int i = 0; i < 60; i++){
        for(int j = 0; j < i; j++){
            if(base[i] & (1ll << j)){
                base[i] ^= base[j];
            }
        }
        if(base[i])    cnt++;
    }
    return cnt;
} 

用上面的操作,让线性基内的元素互相异或,使得线性基内的元素相对最小,可以证明这样的线性基是唯一的。这样的线性基中任意两个数异或,都会增大

为了得到异或和第k小,如下图所示,给每一行编一个号:

通过上面图,我们知道,用这个线性基异或的最小值一定是第(1)行,第2小就是第(3)行(跳过全0行),但是注意第三小是第(1)行异或第(3)行,第四小是第(4)行,第五小是第(4)行异或第(1)行,依次内推;

于是就有一个规律,我们想要知道第k小,就看k的二进制表示其中二进制表示中哪几位为1,就哪几行一起异。比如求第5小 ,其中5的二进制表示为101,本来是第(1)行和第(3)行异或,但第(2)行为0,所以是第(4)行和第(1)行异或。

除此之外,还要注意一点:线性基内的元素不可能异或出0,但是原数组中可能存在异或和等于0的情况如果原数组中的数都成功插入到线性基中,那么原数组一定不能异或出0,否则一定能异或出0。可以在线性基构造中做一个标记

bool zero = false;//标记
bool insert(int x){//线性基插入
    for(int i = 60; i >= 0; i--){
        if(x & (1ll << i)){
            if(base[i])    x ^= base[i];
            else{
                base[i] = x;
                return true;
                break;
            }
        }
    }
    zero = true;
    return false;
} 

那么,如果线性基构造完毕之后,zero标记为true,说明原数组能异或出0

代码如下:

int build(){
    int cnt = 0;
    for(int i = 0; i < 60; i++){
        for(int j = 0; j < i; j++){
            if(base[i] & (1ll << j)){
                base[i] ^= base[j];
            }
        }
        if(base[i])    cnt++;
    }
    return cnt;
}
ll getKXor(int k){
    if(zero && !--k)    return 0;//如果k=1且zero为true,那么异或和第一小就为0
    int cnt = build();//改造线性基
    if(k >= (1ll << cnt))    return -1;//k超过一定范围就不存在
    ll ans = 0;
    for(int i = 0; i <= cnt; i++){
        if(base[i]){//跳过线性基内全0数
            if(k & 1){
                ans ^= base[i];
            }
            k >>= 1;
        }
    }
    return ans;
}

例题



1.2019牛客暑期多校训练营(第一场)----H-XORhttps://blog.csdn.net/weixin_43702895/article/details/97683338

原文地址:https://www.cnblogs.com/qiyueliu/p/11642947.html

时间: 2024-10-08 18:37:56

学习:数学----线性基的相关文章

线性基 学习笔记

ps:做CF的时候碰到了一个线性基的概念,然后在网上学习了一下,发现相关的资料很少,所以打算来写一个我个人的理解. 线性代数中 有极大线性无关组和空间的基的概念.  线性基的性质与此类似. 首先来看一个问题: 给出N个数,要从中选出一个最大的子集,使得子集中的任意个元素异或值不为0. 这个和极大线性无关组有些类似.异或可以看出是模2域下的加法运算,如果把一个数转化为二进制,对应成一个由01构成的向量, 所有这些向量就构成了一个线性空间. 原问题就转化为求这个向量组的极大线性无关组,把这样一个极大

线性基学习

之前是假会,现在是真会.而且我还写了博客.不写博客的东西总有一天会忘. 概述 对于线性基中所有的子集的异或和的集合与线性基中的插入所有数的所有子集的异或和的集合相等. 即线性基代表了原数集中的所有异或和. 线性基中所有子集的异或和两两不同.继而不同的异或和数=\(2^{|线性基|}\). 操作 插入 从高位往低位枚举,若这位上已有数,则异或上这个数后继续,否则将这位赋为当前的数. 查是否存在 从高位往低位枚举,若这位上有数则异或上这个数.当当前值在某时刻=0时说明存在. 查最大异或和 开始答案=

数据结构学习笔记——线性表的应用

数据结构学习笔记——线性表的应用 线性表的应用 线性表的自然连接 计算任意两个表的简单自然连接过程讨论线性表的应用.假设有两个表A和B,分别是m1行.n1列和m2行.n2列,它们简单自然连接结果C=A*B(i==j),其中i表示表A中列号,j表示表B中的列号,C为A和B的笛卡儿积中满足指定连接条件的所有记录组,该连接条件为表A的第i列与表B的第j列相等. 如:         1 2 3                3 5 A  =  2 3 3         B =  1 6       

hdu3949(线性基,求第k小的异或和

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3949 XOR Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 4731    Accepted Submission(s): 1658 Problem Description XOR is a kind of bit operator, we

[TJOI2008] 彩灯 (线性基)

[TJOI2008] 彩灯 题目描述 Peter女朋友的生日快到了,他亲自设计了一组彩灯,想给女朋友一个惊喜.已知一组彩灯是由一排N个独立的灯泡构成的,并且有M个开关控制它们.从数学的角度看,这一排彩灯的任何一个彩灯只有亮与不亮两个状态,所以共有\(2^N\)个样式.由于技术上的问题,Peter设计的每个开关控制的彩灯没有什么规律,当一个开关被按下的时候,它会把所有它控制的彩灯改变状态(即亮变成不亮,不亮变成亮).假如告诉你他设计的每个开关所控制的彩灯范围,你能否帮他计算出这些彩灯有多少种样式可

线性基初探

关于线性基的学习与理解 1.线性基: 若干数的线性基是一组数\(a_1,a_2,...a_n\)其中\(a_x\)的最高位的1在第x位. 通过线性基中元素\(xor\)出的数的值域与原来的数\(xor\)出数的值域相同. 2.线性基的构造法: 对每一个数\(p\)从高位到低位扫,扫到第\(x\)位为1时,若\(a_x\)不存在,则\(a_x=p\)并结束此数的扫描,否则令\(p=pxora_x\). 3.查询: 用线性基求这组数\(xor\)出的最大值:从高往低扫\(a_x\),若异或上\(a_

codeforces 1100F Ivan and Burgers 线性基 离线

题目传送门 题意: 给出 n 个数,q次区间查询,每次查询,让你选择任意个下标为 [ l , r ] 区间内的任意数,使这些数异或起来最大,输出最大值. 思路:离线加线性基. 线性基学习博客1 线性基学习博客2 对于此题,先把区间按照 r 从小到大排序,然后依次处理这些区间,每次插入线性基时,优先保留下标比较大的线性基.查询时,只异或上下标大于 l 的值. 记住异或的符号的优先级很低,所以  if( res^p[i] > res )这样的代码是会wa死的,要注意(这道题这么写,样例都过不了) #

Xor HYSBZ - 2115 (线性基)

Xor HYSBZ - 2115 题意:给一个树,求1到n的最长路径.这里的路径定义为异或和. 线性基~~ 1 #include <bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 struct LiBase{ 5 ll a[63]; 6 //初始化 7 void init(){ 8 memset(a,0,sizeof(a)); 9 } 10 //插入 11 bool insert_(ll x){ 12 for(int

线性基小节

1.线性基的异或集合中每个元素的异或方案唯一. 2.线性基二进制最高位互不相同. 3.线性基中元素互相异或,异或集合不变. 摘自百度文库 线性基能相互异或得到原集合的所有相互异或得到的值. 线性基是满足性质1的最小的集合 线性基没有异或和为0的子集. 证明: 反证法:设线性基S={a1,a2...,an}: 若有子集a1^a2^...^at=0,则a1=a2^a3^...^at,则舍弃a1后一定能通过剩余的元素异或出所有需要a1参与异或的值.设Y=a1^X,因为{a1,a2,...,an}是一组