线性基——认识与理解

参考资料:
《工程数学线性代数》
线性代数的本质
线性基-百度百科
线性基学习笔记
线性基杂谈
一看这些参考资料就知道我也只是个线代萌新而已啊……所以不要把我说的东西太当真……

向量空间

一个向量空间包含一个向量集合,一种向量加法,一种标量乘法,一个标量域

线性无关

对于向量空间(域为 A A )中的n个向量 V1,V2,V3...Vn V 1 , V 2 , V 3 . . . V n ,若方程 a1V1+a2V2+a3V3+...+anVn=0(aiA) a 1 V 1 + a 2 V 2 + a 3 V 3 + . . . + a n V n = 0 ( a i ∈ A ) 只有一组解 a1=a2=...=an=0 a 1 = a 2 = . . . = a n = 0 ,那么这些向量就是线性无关的,反之,它们是线性相关的。
如果一组向量是线性相关的,那么一定有一个向量可以被其他向量表示,这个向量就是“不必须的”。
这么看好像那个向量还挺惨的,没有存在的价值什么的......

向量张成空间

向量组 (V1,V2,V3...Vn) ( V 1 , V 2 , V 3 . . . V n ) 张成的空间是形如 V=a1V1+a2V2+...+anVn(aiA) V = a 1 V 1 + a 2 V 2 + . . . + a n V n ( a i ∈ A ) 的向量组成的集合。

基,乃是基本,一切都得按照基本法来
若向量空间 V V 中的一个向量组T是线性无关的,并且棵以张成 V V ,则称这个向量组T是向量空间 V V 的基。
其中,T的任何真子集不能张成 V V
任何真包含T的向量集都是线性相关的

线性基

现在有很多很多很多的二进制 a0,a1,a2...an a 0 , a 1 , a 2 . . . a n ,我们xjb把它们乱取乱异或一通,然后可以得到很多很多很多新的二进制数。如果我们要统一处理这些数,肯定不能真的把这些数都弄出来。那么,有没有什么简单的表示法,能够“代表”这些被异或出来的二进制数呢?
可以!如果我们把每个二进制数都看成一个向量,比如1011就看成向量 (1,0,1,1) ( 1 , 0 , 1 , 1 ) ,那么把异或当成这个向量空间的基本运算,域看作 0,1 0 , 1 ,那么只要求出一组基,就能够张成所有异或出来的数了。
在二进制下,每一位只有两个状态,“存在,不存在”

To be, or not to be: that is a question.

我们用高斯消元的方法来弄线性基。
首先,对于 ai a i ,我们从高位到低位查看每一位,如果当前位数是1,那么就查看高斯消元矩阵的第 j j 行,假如j j j 列是1,就将ai每一位异或与第 j j 行每一位做异或,继续处理。否则将ai放置在第 j j 行,然后消元即可。
代码如下。

for(int i=1;i<=n;++i) {
    LL x=read();
    for(int j=60;j>=0;--j) if(x&bin[j]){//bin[i]:二进制下第i位
        if(a[j]) x^=a[j];
        else {
            ++js,a[j]=x;
            for(int k=j-1;k>=0;--k) if(a[k]&&(a[j]&bin[k])) a[j]^=a[k];
            for(int k=j+1;k<=60;++k) if(a[k]&bin[j]) a[k]^=a[j];
            break;
        }
    }
}

如果矩阵第i i i 列存在,则说明第i位是可以独立存在的。
当然,就算第 i i i列不存在,也并不代表这一位不能被异或出来。若 k>i k > i ,则第 k k i列一定为0。若 j<i j < i ,第 j j i列可能不为0,如果不为0,则表示异或出的一个数,如果第 i i 位是1,那么第j位一定是1.
我们这样求线性基的时候,会有一些 ai a i ,异或着异或着变成了0,失去了加入矩阵的机会。这样的向量其实还是有点用的,可以算作线性基里的0向量。
当然有的时候,你并不需要将矩阵消元得那么彻底,可以只将其消成一个上三角矩阵,就可以求得有多少非0向量了。
那么就可以简化:

for(int i=1;i<=n;++i) {
    LL x=read();
    for(int j=60;j>=0;--j) if(x&bin[j]){//bin[i]:二进制下第i位
        if(a[j]) x^=a[j];
        else {++js,a[j]=x;break;}
    }
}

实战应用

HDU3949
首先求一遍线性基,然后由于线性基里的向量异或可以表示这些数异或起来的所有向量,而对于矩阵第 i i 行的向量,若非0,则异或了它,第i位就为1,一定比不异或它的那个数要大。
每一次异或第 i i 行的向量,就相当于产生了2i1个比当前数小的,原序列异或出来的数。因此,我们只要把所有非0向量拎出来,组成 b1,b2...bcnt b 1 , b 2 . . . b c n t ,如果 k k 的二进制下第i位为1,那么答案就要异或 bi b i
当然,你如果手玩几个过程,会发现如果原序列可以异或出0的话,这样算出来的答案会大了。所以对于可以异或出0的情况,我们计算答案时要使 k k 减小1.

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL read() {
    LL q=0;char ch=' ';
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+(LL)(ch-'0'),ch=getchar();
    return q;
}
const int N=10005;
int T,n,hav0,cnt,Q;LL bin[61],a[61],b[61];
void init() {
    n=read();int js=0;
    for(int i=0;i<=60;++i) a[i]=0;
    for(int i=1;i<=n;++i) {
        LL x=read();
        for(int j=60;j>=0;--j) if(x&bin[j]){
            if(a[j]) x^=a[j];
            else {
                ++js,a[j]=x;
                for(int k=j-1;k>=0;--k) if(a[k]&&(a[j]&bin[k])) a[j]^=a[k];
                for(int k=j+1;k<=60;++k) if(a[k]&bin[j]) a[k]^=a[j];
                break;
            }
        }
    }
    hav0=(js!=n),cnt=0;//如果非0向量个数不等于n,则存在0向量
    for(int i=0;i<=60;++i) if(a[i]) b[++cnt]=a[i];
}
LL query(LL x) {
    if(hav0) --x;//存在0向量,则x要减1
    if(x>=bin[cnt]) return -1;//一个线性基里的向量都不取是不可以的,所以x=bin[cnt]也是不行的
    LL re=0;
    for(int i=1;i<=cnt;++i) if(x&bin[i-1]) re^=b[i];
    return re;
}
int main()
{
    T=read();
    bin[0]=1;for(int i=1;i<=60;++i) bin[i]=bin[i-1]<<1;
    for(int kas=1;kas<=T;++kas) {
        printf("Case #%d:\n",kas);
        init(),Q=read();
        while(Q--) printf("%lld\n",query(read()));
    }
    return 0;
}

bzoj2844
如果不考虑重复数的情况,那么同样将非0基拎出来排好。对于第i位(当然 Q Q 的第i位也要为1,否则跳过),假如不异或这一位的那个线性基里的向量,那么产生的数就会比 Q Q 小,无论第0到i-1位非0向量是否被异或。因此就会产生2i个小于 Q Q 的数。
考虑重复数的情况,也就是是否异或0向量的情况。比Q小的数,异或上0向量,不变,所以还是比 Q Q 小。因此,假设前面我们算出的比Q小的数有 pos p o s 个,非0向量有 cnt c n t 个,那么答案就是 pos2ncnt+1 p o s ∗ 2 n − c n t + 1

#include<bits/stdc++.h>
using namespace std;
int read() {
    int q=0;char ch=' ';
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
    return q;
}
const int mod=10086,N=100005;
int n,Q,cnt,ans,bin[31],a[N],b[N];
int qm(int x) {return x>=mod?x-mod:x;}
int main()
{
    n=read();
    bin[0]=1;for(int i=1;i<=30;++i) bin[i]=bin[i-1]<<1;
    for(int i=1;i<=n;++i) {
        int x=read();
        for(int j=30;j>=0;--j) if(x&bin[j]) {
            if(!a[j]) {a[j]=x;break;}
            x^=a[j];
        }
    }
    Q=read();
    for(int i=0;i<=30;++i) if(a[i]) b[++cnt]=i;
    for(int i=1;i<=cnt;++i) if(Q&bin[b[i]]) ans=qm(ans+bin[i-1]%mod);
    for(int i=1;i<=n-cnt;++i) ans=qm(ans+ans);
    ans=qm(ans+1),printf("%d\n",ans);
    return 0;
}

bzoj2115
猜结论:从1到n的任意一条路径上的异或和,一定可以表示为随意一条路径异或上若干环的异或和的值。
证明:我走初始路径,忽然离开,走一个环,回来,来回路径上的权值异或两次没了,所以成立。
而异或的这个环如果包含初始路径,则相当于换路径,依然成立。
所以我们就利用dfs树上的返祖把环的异或值都整出来,随便取一条1到n的异或值作为答案初值,对环的异或和求线性基,从高位到低位看这些向量,如果答案异或上它可以增大,就异或上它。
由于是从高位到低位贪心,所以正确性是显然的。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL read() {
    LL q=0;char ch=' ';
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+(LL)(ch-'0'),ch=getchar();
    return q;
}
const int N=50005,M=200005;
int n,m,tot,js;LL ans;
int h[N],ne[M],to[M],vis[N];
LL w[M],bin[61],dis[N],a[M],b[61];
void add(int x,int y,LL z) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,w[tot]=z;}
void dfs(int x) {
    vis[x]=1;
    for(int i=h[x];i;i=ne[i]) {
        if(!vis[to[i]]) dis[to[i]]=dis[x]^w[i],dfs(to[i]);
        else a[++js]=dis[x]^dis[to[i]]^w[i];
    }
}
void work() {
    for(int i=1;i<=js;++i)
        for(int j=60;j>=0;--j) if(a[i]&bin[j]) {
        if(b[j]) a[i]^=b[j];
        else {
            b[j]=a[i];
            for(int k=0;k<j;++k) if(b[k]&&(b[j]&bin[k])) b[j]^=b[k];
            for(int k=j+1;k<=60;++k) if(b[k]&bin[j]) b[k]^=b[j];
            break;
        }
    }
    for(int i=60;i>=0;--i) if((ans^b[i])>ans) ans=ans^b[i];
    printf("%lld\n",ans);
}
int main()
{
    int x,y;LL z;
    n=read(),m=read();
    bin[0]=1;for(int i=1;i<=60;++i) bin[i]=bin[i-1]<<1;
    for(int i=1;i<=m;++i)
        x=read(),y=read(),z=read(),add(x,y,z),add(y,x,z);
    dfs(1),ans=dis[n],work();
    return 0;
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值