参考:
线性基(linear basis??)
介绍:
性质:
-
线性基能相互异或得到原集合的所有相互异或得到的值。
-
线性基是满足性质1的最小的集合
-
线性基没有异或和为0的子集(显然,当a1^a2^..^an=0时,a1=a2^...^an a1是可以通过其他元素表出的,没有用,与性质2矛盾)
(摘自:百度百科)
简单来说,线性基的本质是通过最少的数(a1,a2...an),能够表示出所有原来的数所能表示出的数。
这个{an}数集就是线性基。
可以这样理解,线性基就是对于一个数集的压缩。
举个例子:a,b,c 且 a^b=c
那么a,b,c能通过异或表示出的所有数,a,b通过异或都能表示。也可以得出,c在异或意义下可以由a,b表出
一:
基于以上的理解和性质,
线性基最经典的应用是可以处理异或和最大问题。
也就是,给出若干个数,从中选择若干个数,使得它们的异或和最大
(胡乱贪心:
1.选择一个数开始,如果能^后增大答案就选上。
反例:1010,1111 假设会选择1010,就选不上1111了。
2.那我先从大到小排个序考虑?
反例:11110,10101,01010,答案是10101^01010但是会选择11110
)
正解:
因为,a^b^b=a,a^0=a
所以,1111,1101,10,1,110,1001,这些个数,都能用1000,100,10,1通过异或表出。
所以,随便异或异或异或爱咋滴咋滴,反正都能异或回去。
这是一个直观的考虑,我们也可以不必都变成2^k
我们只要每个最高位的1都留下一个就好了。
具体方法如下:
p[j] 从小到大第j位为1的第一个数值(不一定是原始的数,可能通过异或得出)
for 向线性基中插入所有的数,
for 最高位->最低位 每一位
if 这一位没有
p[j]=x;退出
else x^=a[p[j]] 去掉这一位
这样,每个插入的数只有两个结局:要么进入线性基,要么变成0
查最大值时,直接贪心地从最高位的p[j]开始。
能增大ret就选上。
(证明:
因为这一位为1的仅有这一个数。
若^后ret不变大,说明ret这一位一定是1
若^后ret变大,说明这一位一定是0,而却不选,那么之后ret一定只能为0了,不优。
)
代码:luogu P3812 【模板】线性基
#include<bits/stdc++.h> using namespace std; typedef long long ll ; const int N=55; int n; struct bas{ ll a[N]; void insert(ll x){ for(int i=55;i>=0;i--){ if(x&(1LL<<i)){ if(!a[i]){ a[i]=x;return; } x^=a[i]; } } } ll query(){ ll ret=0; for(int i=55;i>=0;i--){ if((ret^a[i])>ret) ret^=a[i]; } return ret; } }lb; int main() { scanf("%d",&n);ll t; for(int i=1;i<=n;i++){ scanf("%lld",&t);lb.insert(t); }printf("%lld",lb.query());return 0; }
二:
另外,线性基还能处理别的问题:
例题:
[JLOI2015]装备购买
题意:
n 个装备,每个装备 m 个属性,每个装备还有个价格。如果手里有的装备的每一项属性为它们分配系数(实数)后可以相加得到某件装备,则不必要买这件装备。求最多装备下的最小花费。求装备个数及最小花费。
也就是说,每个物品是一个m维向量,选择出来的物品,不能线性相关。
(补充线性相关:存在不全为0的实数k1,k2,...kn,使得a1k1+a2k2+...+ankn=0 (其中a1~an是若干维的向量)则这些向量线性相关,否则线性无关
换句话说,如果存在一个向量可以通过其他的向量线性表出,那么这些向量就线性相关。
)
发现,线性相关问题和异或问题的相似点在于:
如果一些向量{a}可以表出的所有向量是{b},那么,{a}中向量的各种线性运算后,还是能表示出{b},不多不少
所以,可以仿照之前的p[j],
定义:p[j]表示,第j维向量里,第一个不为0的物品编号(该物品可能已经通过线性运算改变了属性值)
显然为了话费最小,sort按照cost处理
for 所有物品
for 该物品所有属性
if 这个属性维不是0{
if p[j]没有
p[j]=i ,sum+=cost,cnt++;break
else
像高斯消元一样,消除这一维,后面所有维(前面可以不用管,因为之后给别的消也用不上)
这个物品的属性也随之改变,没有关系,因为线性改变不会影响线性表出结果。
}
对于每一个物品,有两种结果:
1.消除到一半,发现p[j]没有,加入线性基,退出。
2.消除完了,最后各属性都是0了。相当于可以通过之前加入的所有物品线性表出。不能加入。
(不会存在p[j]都满了,但是消完m位后,有一位不是0的情况。
因为之前选择的m个向量彼此之间线性无关,可以作为m维空间的基底,线性表示所有的向量了。
换句话说,加入了最多m个向量后,答案就出来了。
)
代码:
#include<bits/stdc++.h> using namespace std; typedef long double lb; const int N=500+10; const lb eps=1e-8; int n,m; struct node{ int cos; lb x[N]; }a[N]; int p[N]; bool cmp(node a,node b){ return a.cos<b.cos; } int cnt,sum; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++) scanf("%Lf",&a[i].x[j]); } for(int i=1;i<=n;i++) scanf("%d",&a[i].cos); sort(a+1,a+n+1,cmp); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(fabs(a[i].x[j])>eps){ if(!p[j]){ p[j]=i;cnt++;sum+=a[i].cos;break; } else{ lb t=a[i].x[j]/a[p[j]].x[j]; for(int k=j;k<=m;k++) a[i].x[k]-=t*a[p[j]].x[k]; } } } } printf("%d %d",cnt,sum);return 0; }
为什么是对的?
因为线性运算对于线性表出没有影响啊。
我们把它减去了若干倍,还是可以的。
那么我们相当于是怎样实现的线性表出的验证呢??
比如:三个物品a0,b0,c0,
线性基之前加入了a0,b1, b1=b0-k0a0
对于c0,我们会验证:
它是不是a0“倍数”? c0-=k1*a0 == 0?
它是不是可以用a0,b0表出? c0-=k1*a0 + k2* b1 ==0 ?
( 若ax+by=z, a,b有实数解,那么,a'x+b'(y-k*x)=z 即: (a'-k)x + b'*y =z 对于a',b'也有实数解。)
它是不是可以用b0表出? c0-=k2*b1==0?假设c0是b0的p倍,
因为,b1是减去k0倍a0,所以,c0在a0的时候,会减去pk0的a0,减完之后,c0还是b1的倍数。
另外,因为我们贪心按照cost排序,所以先插入的一定是最优的价值。
总结:
线性基通过提取数集中最少的元素,可以办到原数集中所有的数可以办到的事。
线性基:数集压缩自动机