网上大佬写的博客。。。我以为自己看懂了(假装看懂)。。。结果第二题就被卡住了。。。看不懂了。。

https://blog.sengxian.com/algorithms/linear-basis

https://www.cnblogs.com/vb4896/p/6149022.html

是我水平不够,看起来有点费劲(;´༎ຶД༎ຶ`)智商堪忧

还是视频看起来轻松很多:青涩的讲解,但是能听懂,给up主点赞!

目前的理解:线性基就是通过异或构建行阶梯矩阵,找到最大线性无关的向量组,去解决有关XOR的一些问题

数x对应的二进制为: bj=0或者1,用p[]存下面矩阵中的数

所谓线性基,就是线性代数里面的概念。一组线性无关的向量便可以作为一组基底,张起一个线性的向量空间,这个基地又称之为线性基。

第一个例子:


,插入的时候从高位向低位看起

--->插入a[0]=7=0111:a[0]的=0,p[2]=a[0]

矩阵:

--->插入a[1]=2=0010:a[1]的=1,p[1]=a[1],后面的位k,若,那么p[1]^=p[k](p[k]的),消掉p[1]中后的1,让p[1]中只有=1;同理,p[1]上面的行j,如果p[j]中的=1,那么p[j]^=p[1],消掉p[j]中位上的1,保证p[j]中只有

矩阵:

--->插入a[2]=3=0011,从高位向低位看起,因为=1的位置已经存入数a[1]了,所以为了保证我们要构建的是行阶梯矩阵,a[2]^=p[1],新的a[2]=0001,其中,p[0]=a[2],和插入a[1]时相同的做法,用下面的行消去自己,用自己消去上面的行中不应该为1的1:

矩阵:

--->插入a[3]=4=0100,,但是p[2]已经存入数了,a[3]^=p[2]=0,无法插入

至此,线性基对应的行阶梯矩阵已经构建完了,此外,通过构建过程可以知道最终的矩阵可以转化为如下内容:

p[2]、p[1]、p[0]都不为0,所以线性基的大小为num=3,a[4]中n=4,num<n

p[2]、p[1]、p[0]对应的向量是一个最大线性无关的向量组,因为num<n所以a[]中的数可以通过异或得到0,这点可以通过插入a[3]时出现0得到验证。当num=n时,这n个向量组成最大线性无关的向量组,根据线性无关的定义可以知道只有这些向量前面的系数都为0时才可以得到0。

注意:这里的p[2]、p[1]、p[0]进行异或相当于其对应的向量进行加法运算(二进制加法,没有进位)。

用新的数组L(或者一个vector<>)存不为0的p[i],若求a[4]所能构成的异或的数中的第k小,k=,新的矩阵有如下对应关系:

若求第k=1小,因为num!=n,所以可以异或出0,那么可异或出第1小的数就是0

若求第k=4小,因为num!=n,所以可以异或出0,所以k=k-1=3=0011,,那么可异或出第1小的数就是L[0]^L[1]等价于

第二个例子:


--->插入a[0]=9=1001,p[3]=a[3]

矩阵:

--->插入a[1]=11=1011,p[3]已经有数了,a[1]^=p[3]=0010,p[1]=a[1],用下面的行消自己,用自己消下面的行(但都莫得消)

矩阵:

--->插入a[2]=5=0101,p[2]=a[2],用下面的行消自己,用自己消下面的行(但都莫得消)

矩阵:

最终的矩阵相当于:其中线性基的大小num=3,n=3

用新的数组L存不为0的p[i],求a[3]能异或成的第k小,k=:,对应关系如下:

因为线性基大小num=n,所以不能异或出0

若k=1=0001,,所以第1小的数就是L[0]

若k=5=1001,,所以第5小的数就是L[0]^L[3]

综上所述:


 构建行阶梯矩阵时,若a[i]=,从高位往地位第一个为1的位是j,若p[j]已经存入数了,那么a[i]^=p[j],否则直接p[j]=a[i]

 在p[j]插入a[i]后,让p[j]下面的行消去p[j]中j位后面的1,让p[j]消去p[j]上面的行中j位上的1,目的是为了尽量使p[j]中只有j位是1

 若n个数构成的线性基的大小num=n,则对应的行阶梯矩阵的秩=n,这n个向量线性无关,那么这个n个数不能通过异或得到0,否则可以得到0

 在求n个数通过异或构成的第k小的数时,用新的数组L(或者 vector<>)存那些不为0的p[i]

     num==n,不能得到0,第k小的数就是k的二进制位上为1的位所对应的L[]值,比如K=1001,那么结果就是L[0]^L[3]

     num!=n,不能得到0,k=k-1,结果就是k的二进制位上为1的位所对应的L[]值

     num==n时,这个n个数最多能构成-1个不同的数;否则可以构成个不同的数

 L[i]^L[j]或者p[i]^p[j] 其实就是a[]上的一些数在异或,只不过目前我们不关心是哪些数在异或(当然也可以通过记录得知),我们只在意能异或成的数的大小

 求n个数能构成的最大值,就直接把L[]中的值都异或起来就OK啦!

 

终于写完原理了٩(˃̶͈̀௰˂̶͈́)و总算是懂了线性基了☆〜(ゝ。∂)

一般1≤a[i]≤1e18,最多60位,所以max_base设成60,p[max_base+3],max_base别设置太大了,可能结果会错,我试过(´・Д・)」,就60吧!

模版:


hdoj3949

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
const int max_base=60;
ll t,n,q,x;
ll a[maxn],p[maxn+3];
vector<ll> v;
void solve()
{
    v.clear();//清空啊啊啊!!!
    memset(p,0,sizeof(p));//初始化啊啊啊!!!
    for(int i=0;i<n;i++)
    {
        for(int j=max_base;j>=0;j--)//从高位向低位
        {
            if(a[i]>>j==0) continue;//该位上为0,对线性基这位没有有贡献
            if(!p[j])//该行没数,直接填入
            {
                p[j]=a[i];
                for(int k=j-1;k>=0;k--)
                    if(p[k] && p[j]>>k&1)
                        p[j]^=p[k];//下面的行消自己
                for(int k=j+1;k<=max_base;k++)
                    if(p[k]>>j&1)
                        p[k]^=p[j];//自己消上面的行
                break;//填入之后结束每一位上的遍历
            }
            else
                a[i]^=p[j];//该行已经有数
        }
    }
    for(int i=0;i<=max_base;i++)
        if(p[i])
            v.push_back(p[i]);

}
ll query(ll x)
{
    if(v.size()!=n) x--;
    if(x==0) return 0;//能得到0,第1小的数是0
    if(x>=(1LL<<v.size())) return -1;//能得到0,x已经-1了,x最多达到2^v.size()-1;不能得到0,x最多就是v.size()-1
    ll ans=0;
    for(int i=0;i<=max_base;i++)
        if(x>>i&1)
            ans^=v[i];
    return ans;
}
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt", "r", stdin);
    scanf("%lld",&t);
    for(int i=1;i<=t;i++)
    {
        printf("Case #%d:\n",i);
        scanf("%lld",&n);
        for(int j=0;j<n;j++)
            scanf("%lld",&a[j]);
        solve();
        scanf("%lld",&q);
        for(int j=0;j<q;j++)
        {
            scanf("%lld",&x);
            printf("%lld\n",query(x));
        }
    }
    return 0;
}

SGU275:但是好像没法补题,求n个数异或得到的最大值,直接把非0的p[i]异或起来就完事了

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
const int max_base=60;
ll n;
ll a[maxn],p[maxn+3];
void solve()
{
    for(int i=0;i<n;i++)
    {
        for(int j=max_base;j>=0;j--)//从高位向低位
        {
            if(a[i]>>j==0) continue;//该位上为0,对线性基这位没有有贡献
            if(!p[j])//该行没数,直接填入
            {
                p[j]=a[i];
                for(int k=j-1;k>=0;k--)
                    if(p[k] && p[j]>>k&1)
                        p[j]^=p[k];//下面的行消自己
                for(int k=j+1;k<=max_base;k++)
                    if(p[k]>>j&1)
                        p[k]^=p[j];//自己消上面的行
                break;//填入之后结束每一位上的遍历
            }
            else
                a[i]^=p[j];//该行已经有数
        }
    }
}
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt", "r", stdin);
    ll ans=0;
    scanf("%lld",&n);
    for(int i=0;i<n;i++)
        scanf("%lld",&a[i]);
    solve();
    for(int i=0;i<=max_base;i++)
        if(p[i])
            ans^=p[i];
     printf("%lld\n",ans);
    return 0;
}