学习于这个博客
首先,线性基是一个集合,对于任何一个序列一定有至少一个线性基,取线性基中的某些数异或起来一定可以得到原序列中的任何一个数。
线性基三大性质:
1.原序列中的任何一个数都可以由线性基里面的一些数异或得到。
2.线性基中任意数异或起来都得不到0(换句话说,一个数只能由线性基中特固定的几个数异或得到)。
3.线性基中数的个数是唯一的,并且在满足所有性质的条件下个数是最少的。
线性基构造函数:
void add(long long x){
int i;
for(i=60;i>=0;i--){
if(x&(1(long long)<<i)){
if(d[i])
x^=d[i];
else{
d[i]=x;
break;
}
}
}
}
d数组有个性质,d[i]如果不为0的话,d[i]的二进制的第(i+1)位是1且为最高位。
性质1证明:
对于对于加入的任何一个数,从最高位向最低位进行异或,而d[i]的(i+1)位一定是1且位最高位,所以x一定是递减的,如果碰到某一位d数组中没有被赋值,则进行赋值然后x变为0,如果碰不到没有赋值的d数组,那x最终也为0,所以根据异或可知最开始添加的数x一定能用d数组组合异或得到。
性质2证明:
假设在d数组中任选一些数,那么最大的d[i]的最高位1没有被异或成0,所以不可能得到0。
性质3证明:(这个证明是借鉴的别人的,非自己证明)
线性基中数的个数本质上就是插入成功的个数,如果x全部都插入成功,那么线性基中数的个数即为x的个数,如果某一个x插入失败,则x=d[a] ^ d[b] ^ d[c],如果想让x插入成功,则为d[a] ^ d[b] ^ x=d[c],这样虽然x插入成功了但是d[c]插入失败,所以x无论插入到哪里,最后一个数一定会插入失败,而且x=d[a] ^ d[b] ^ d[c]是固定唯一的,不可能由其它的数组合异或而成,所以插入成功数肯定是固定的数量,所以线性基个数一定是唯一的,只是数组的值可能会因为插入顺序而改变。
这三个性质都是很重要的,需要熟记,特别是性质二是特别重要且易忽略的,需要重点记住!!!
应用1:
求一个序列某些数的异或和的最大值
long long ans=0;
for(i=60;i>=0;i--){
if((ans^d[i])>ans){
ans^=d[i];
}
}
很明显的贪心策略,但是有一个很重要的问题。
为啥任意d[i]都能被随便选择呢?
这里证明一下,首先假设原序列中有一个数x=d[a] ^ d[b] ^ d[i],d[i]是x插入成功的位置,这样则可以证明a和b都大于i,然后d[a]和d[b]也可以向上推导,最终一定成为了某一位最大的d[j],而这个d[j]一定是原序列中的某一个数(因为没有数把它异或掉一部分),然后把这些数去重异或就能得到任意d[i]了。
应用2:
求一个序列某些数异或和的最小值
思路:先看看有没有插入失败的,如果有就直接为0,否则答案就是d数组中最小的非0数。
应用3:
求一个序列某些数异或和的所有值的第k小
思路:将k转化为二进制,将d数组中0去除然后从小到大排序,如果k&(1<<i)则异或上第i个元素即可。
这个其实就是贪心+组合,d数组中的每个数都可以选择,因此就可以根据二进制搞了。
前缀线性基:
前缀线性基就是具有线性基的所以功能并可以区间查询这些功能。
区间查询(l,r)时,将查询d[r][i]数组即可,然后对于pos[r][i]小于l的直接continue掉即可。
这个前缀线性基有个比较难理解的地方就是swap部分,我一开始总感觉如果进行了x和d[i]的交换,那么就会影响之前的X的插入,但是其实可以这么想,如果X出现的比在d[i]上插入成功的那个原数组的y早,那么明显该有影响而且因为y替换了X所以把影响抵消了,而如果X出现的比y晚,那么X一定已经把y替换掉了,所以不会出现这种情况,因此这种交换是可行的。
int cnt=0;
void add(long long x){
int i;
cnt++;
for(i=0;i<=60;i++){
d[cnt][i]=d[cnt-1][i];
pos[cnt][i]=pos[cnt-1][i];
}
int P=cnt;
for(i=60;i>=0;i--){
if(x&(1(long long)<<i)){
if(d[cnt][i]){
if(pos[cnt][i]<P){
swap(pos[cnt][i],P);
swap(d[cnt][i],x);
}
x^=d[cnt][i];
}
else{
d[cnt][i]=x;
pos[cnt][i]=P;
break;
}
}
}
}
HDU6579 前缀线性基模板题
cf- Ivan and Burgers 前缀线性基模板题
#include<iostream>
#include<cstdio>
using namespace std;
const int MAX_N=501000;
int d[MAX_N][32],pos[MAX_N][32];
int a[MAX_N];
int cnt=0;
void add(int x){
int i;
cnt++;
for(i=0;i<=31;i++){
d[cnt][i]=d[cnt-1][i];
pos[cnt][i]=pos[cnt-1][i];
}
int P=cnt;
for(i=31;i>=0;i--){
if(x&(1<<i)){
if(d[cnt][i]){
if(pos[cnt][i]<P){
swap(pos[cnt][i],P);
swap(d[cnt][i],x);
}
x^=d[cnt][i];
}
else{
d[cnt][i]=x;
pos[cnt][i]=P;
break;
}
}
}
}
int ask(int l,int r){
int ans=0,i;
for(i=31;i>=0;i--){
if(pos[r][i]<l)
continue;
if((ans^d[r][i])>ans)
ans^=d[r][i];
}
return ans;
}
int main(void){
int T,n,m,i,op,l,r,x;
cin>>T;
while(T--){
scanf("%d%d",&n,&m);
cnt=0;
for(i=1;i<=n;i++){
scanf("%d",&a[i]);
add(a[i]);
}
int lastans=0;
for(i=1;i<=m;i++){
scanf("%d",&op);
if(op==0){
scanf("%d%d",&l,&r);
l=(l^lastans)%n+1;
r=(r^lastans)%n+1;
if(l>r)
swap(l,r);
//cout<<l<<" "<<r<<" !!!!\n";
lastans=ask(l,r);
printf("%d\n",lastans);
}
else{
scanf("%d",&x);
x=x^lastans;
n++;
add(x);
}
}
}
return 0;
}
#include<iostream>
#include<cstdio>
using namespace std;
const int MAX_N=55;
const long long MOD=2008;
char s[MAX_N];
long long d[MAX_N];
void add(long long x){
int i;
for(i=60;i>=0;i--){
if(x&((long long)1<<i)){
if(d[i])
x^=d[i];
else{
d[i]=x;
break;
}
}
}
}
int main(void){
int n,m,i,j;
scanf("%d%d",&n,&m);
for(i=1;i<=m;i++){
scanf("%s",s+1);
long long x=0;
for(j=1;j<=n;j++){
if(s[j]=='O')
x=x*2+1;
else
x=x*2;
}
add(x);
}
long long ans=1;
for(i=0;i<=60;i++){
if(d[i])
ans=ans*2%MOD;
}
printf("%lld\n",ans);
return 0;
}
P4570 [BJWC2011]元素
按权值排序,然后贪心如果能插入答案就加上这种矿石的权值。
证明:线性基中成功插入的个数是固定不变的,因此当然要插入成功的那些数的权值越大越好,因此就可以按权值排序。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAX_N=1010;
struct skt{
long long x,y;
}a[MAX_N];
long long d[MAX_N];
bool add(long long x){
int i;
for(i=60;i>=0;i--){
if(x&((long long)1<<i)){
if(d[i])
x^=d[i];
else{
d[i]=x;
return true;
//break;
}
}
}
return false;
}
bool cmp(skt a,skt b){
return a.y>b.y;
}
int main(void){
int n,i,j;
scanf("%d",&n);
long long ans=0;
for(i=1;i<=n;i++)
scanf("%lld%lld",&a[i].x,&a[i].y);
sort(a+1,a+n+1,cmp);
for(i=1;i<=n;i++){
if(add(a[i].x))
ans+=a[i].y;
}
printf("%lld\n",ans);
return 0;
}
P3292 [SCOI2016]幸运数字
这个题倍增线性基,就是直接把线性基倍增,然后合并的时候暴力合并logn*logn复杂度,有点东西,但是不想写。