线性基

线性基,可以拿来搞异或一类的东西。它可以表示出原数组互相异或能异或出的所有值。

一些性质:

  1. 线性基的元素能相互异或得到原集合的元素的所有相互异或得到的值。
  2. 线性基是满足性质 1 的最小的集合。
  3. 线性基没有异或和为 0 的子集。
  4. 线性基中每个元素的异或方案唯一,也就是说,线性基中不同的异或组合异或出的数都是不一样的。
  5. 线性基中每个元素的二进制最高位互不相同。

构造:我不太懂。

void ins(int x){//将线性基中插入数 
	for(int i=63;i>=0;i--){
		if(!(x>>i))continue;
		if(!p[i]){//如果当前位有值且线性基当前位为0则改 
			p[i]=x;break;
		}
		x^=p[i];//反之异或当前值 
	}
}
bool query(int x){//查询一个数能否被线性基中数表出 
	for(int i=63;i>=0;i--){
		if((x>>i)&1)x^=p[i];//当前位有就异或一下 
	}
	if(x==0)return false;
	return true;
}

大概原理是线性基的当前位的最高位就是这一位(举个例子,p[60]的最高位是1<<60)。然后贪心就可以搞出来。

判断一组数能异或出的最大值也差不多的贪心就行。

int ans=0;
for(int i=63;i>=0;i--)ans=max(ans,ans^p[i]);

最小值同理,但是注意特判0,直接看有没有元素不能插入线性基就行了。

还有一个结论:设线性基大小为\(size\),原数组大小为\(n\),那么显然有\(2^{size}\)种不同的异或结果。而原数组有\(2^n\)种有重复的结果,每个结果重复\(2^{n-size}\)次(也就是平均分布)。

关于异或 \(k\) 小值,首先判下能否出 \(0\),能就 \(k-1\),然后把 \(k\) 拆位,对应位异或。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
#define int long long
using namespace std;
const int mod=2008;
int n,m;
struct node{
    int p[60];
    bool ins(int x){
        for(int i=50;i>=0;i--){
            if((x>>i)&1){
                if(!p[i]){
                    p[i]=x;return true;
                }
                x^=p[i];
            }
        }
        return false;
    }
}p;
int w[60];
int query(int k){
    int ans=0;
    for(int i=50;i>=0;i--){
        if((k>>i)&1)ans^=w[i+1];
    }
    return ans;
}
signed main(){
    scanf("%lld",&n);bool jud=false;
    for(int i=1;i<=n;i++){
        int x;scanf("%lld",&x);
        if(!p.ins(x))jud=true;
    }
    for(int i=50;i>=0;i--){
        if(!p.p[i])continue;
        for(int j=50;j>i;j--){
            if((p.p[j]>>i)&1)p.p[j]^=p.p[i];
        }
    }
    int cnt=0;
    for(int i=0;i<=50;i++)if(p.p[i])w[++cnt]=p.p[i];
    cnt=1ll<<cnt;
    scanf("%lld",&m);
    while(m--){
        int k;scanf("%lld",&k);k-=jud;
        if(k>=cnt)puts("-1");
        else printf("%lld\n",query(k));
    }
    return 0;
}

然后是一些科技:

  1. 前缀线性基

题意:一个序列,两种操作:区间选数异或最大值,序列末尾加一个数。强制在线。

前缀线性基,顾名思义,我们对每个前缀都建一个线性基,这样我们就只需要统计一个询问\([l,r]\)\(l\)之后的部分对\([1,r]\)的贡献。

然后显然,如果插入两个数的效果相同,这个数的位置越靠后越好。所以我们记录一下线性基中的每个数的位置,如果能更改成位置更优的就更改一下。怎么搞?我们线性基插入的时候,如果一个位置有值且位置更优,就把要插入的数和线性基内的数换一下。代码实现如下:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
struct node{
	int p[32],pos[32];//值和位置 
	void ins(int x,int val){//x位置 val值 
		for(int i=31;i>=0;i--){
			if((val>>i)&1){
				if(!p[i]){
					p[i]=val;pos[i]=x;return;//没有就正常插入 
				}
				else if(pos[i]<x){
					swap(pos[i],x);swap(p[i],val);//有的话就更改一下 
				}
				val^=p[i];//正常流程 
			}
		}
	}
	int query(int l){
		int ans=0;
		for(int i=31;i>=0;i--){
			if(p[i]&&pos[i]>=l)ans=max(ans,ans^p[i]);//只有位置在l后面才更新 
		}
		return ans;
	}
}a[500010];
void update(int x){
	n++;a[n]=a[n-1];a[n].ins(n,x);
}
int find(int l,int r){
	return a[r].query(l);
}
  1. 带修线性基

题意:维护一堆数的线性基,要求插入、删除、查询异或最大值。强制在线。

首先如果不强制在线显然可以线段树分治乱搞。

我们可以维护线性基内所有的原数,插入就正常插入,删除开始分类讨论。(咕)

posted @ 2022-09-03 19:30  gtm1514  阅读(44)  评论(0编辑  收藏  举报