笔记 线性基
定义
在线性代数中,基又称为基地,是刻画向量的工具。对于基底的元素我们称为基向量,向量空间的任意一个元素都可以唯一表示成为基向量的线性组合。同样,线性基也是一种基,它是一种特殊的基,一般用来求异或问题
我不知道你看懂没有,反正我是没看太懂,只需要知道线性基处理异或问题时十分方便就好。
性质
- 将线性基中的一些元素异或一定可以得到完整的原数列
- 原数列中异或的值域与线性基异或的值域相同
- 线性基中没有异或和为0的子集
- 线性基中选取元素的每种方案异或得到的数一定不相同
- 对于一个数列来说,线性基中元素的个数是唯一的,并且是满足以上性质的最小集合
简单证明一下(为了防止有理解问题,用xor表示异或运算):
对于性质一,构建线性基时可以维护,下边再提。
对于性质二,可以由性质一推得。
对于性质三,如果有\(x_1 \ xor \ x_2 ………… xor\ x_n\ =\ 0\),那么一定有\(x_1\ xor\ x_2 ……\ xor \ x_n\ =\ x_i\),就是说\(x_i\)可以由已经有的元素异或得到,根据性质一可以知道,原数列的每一个数都能由线性基中的数异或得到,换言之,\(x_i\)没有必要加入线性基,所以性质三得证。
对于性质四,和性质三的推导大致相同,懒得写了。
对于性质五,综上可得。
基本操作
- 插入
从高到低依次考虑该数字二进制的每一位,若该位是1,那么找到该位对应的线性基,如果是空的,直接替换掉并且退出插入,如果不是空的,那么用该数字异或线性基上的数,然后继续插入,直到找到一个空的线性基或者该数被异或为0,若找到一个空的线性基,则说明插入成功,若被异或成0,说明插入失败,这时可以知道该数字能被线性基中的一个子集异或得到,没有必要插入线性基。
要注意的是左移时考虑左移的是1还是1ll这里很容易挂。
bool Insert(ll w){
for(int i=62;i>=0;i--){
if(w&(1ll<<i)){
if(!bas[i]){
bas[i]=w;
return 1;
}
w^=bas[i];
}
}
return 0;
}
2.查询数列异或和最小值
由线性基的性质可以知道,线性基中的最小的位上的不为0的数即答案,注意判断0的情况。
3.查询数列异或和最大值
根据插入的函数,第i个线性基中的数的最高位上的1是第i位,而高位上放1肯定比放0优,所以尽量让每个高位上都是1。
for(int i=55;~i;i--)
if((ans^p[i])>ans)
ans^=p[i];
或者
for(int i=62;i>=0;i--)
if(b[i]&&((ans&(1ll<<i))==0))
ans^=b[i];
4.查询第k小值,暂时不会。
例题
1.模板
这个就不说了,挺模板的。
2.元素
题意是让我们从一个序列中选出它的一个子集,使得选出来的数异或和不能得到0,且得到的数权值和最大。
异或和不能得到0可以构建一个线性基维护
线性基有一个很好的性质,就是插入顺序对线性基没有影响,可以参照上述性质五,既然这样,加一个大的也是加,加一个小的也是加,那肯定要加入大的啊,所以排一遍序一次考虑能不能加入线性基即可。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int lqs=1e3+10;
struct Node{
ll idx;
int val;
bool operator<(const Node&A)const{
return val>A.val;
}
}p[lqs];
ll bas[70];
bool Ins(ll w){
for(int i=62;i>=0;i--){
if(w&(1ll<<i)){
if(!bas[i]){
bas[i]=w;
return 1;
}
w^=bas[i];
}
}
return 0;
}
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld%d",&p[i].idx,&p[i].val);
}
sort(p+1,p+n+1);
int ans=0;
for(int i=1;i<=n;i++){
if(Ins(p[i].idx))
ans+=p[i].val;
}
printf("%d\n",ans);
}
3.彩灯
这个会发现就是问这个序列的子集异或能得到多少不同的数,然后这几个字符串长度都不超过50,可以转换成十进制数。
将这几个十位数都丢到线性基里边然后线性基的集合个数就是答案,注意一个灯都不开也是一种情况,依据是上述性质一。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int lqs=60;
ll p[lqs],col[lqs];
char s[lqs];
bool Ins(ll w){
for(int i=59;i>=0;i--){
if(w&(1ll<<i)){
if(!p[i]){
p[i]=w;
return 1;
}w^=p[i];
}
}
return 0;
}
int pow(int cnt,int w,int mod){
int ans=1;
while(cnt){
if(cnt&1)ans=1ll*ans*w%mod;
w=1ll*w*w%mod;
cnt>>=1;
}
return ans;
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%s",s);
for(int j=0;j<n;j++)
if(s[j]=='O')col[i]|=1ll<<j;
}
int ans=0;
for(int i=1;i<=m;i++)
if(Ins(col[i]))ans++;
printf("%d\n",pow(ans,2,2008));
}