网上大佬写的博客。。。我以为自己看懂了(假装看懂)。。。结果第二题就被卡住了。。。看不懂了。。
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吧!
模版:
#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;
}