线性基 (暂时先挖个坑)

定义

设数集T

的值域范围为[1,2n−1]。
T的线性基是T的一个子集A={a1,a2,a3,...,an}。
A中元素互相xor所形成的异或集合,等价于原数集T的元素互相xor形成的异或集合。
可以理解为将原数集进行了压缩。

性质

1.设线性基的异或集合中不存在0。
2.线性基的异或集合中每个元素的异或方案唯一,其实这个跟性质1是等价的。
3.线性基二进制最高位互不相同。
4.如果线性基是满的,它的异或集合为[1,2n−1]。
5.线性基中元素互相异或,异或集合不变。

维护

插入

如果向线性基中插入数x,从高位到低位扫描它为1的二进制位。
扫描到第i时,如果ai不存在,就令ai=x,否则x=x⊗ai。
x的结局是,要么被扔进线性基,要么经过一系列操作过后,变成了0。

bool insert(long long val)
{
    for (int i=60;i>=0;i--)
        if (val&(1LL<<i))
        {
            if (!a[i])
            {
                a[i]=val;
                break;
            }
            val^=a[i];
        }
    return val>0;
}

合并

将一个线性基暴力插入另一个线性基即可。

L_B merge(const L_B &n1,const L_B &n2)
{
    L_B ret=n1;
    for (int i=0;i<=60;i++)
        if (n2.d[i])
            ret.insert(n2.d[i]);
    return ret;
}

查询

存在性

如果要查询x

是否存于异或集合中。
从高位到低位扫描x的为1的二进制位。
扫描到第i位的时候x=x⊗ai
如果中途x变为了0,那么表示x存于线性基的异或集合中。

最大值

从高位到低位扫描线性基。
如果异或后可以使得答案变大,就异或到答案中去。

long long query_max()
{
    long long ret=0;
    for (int i=60;i>=0;i--)
        if ((ret^d[i])>ret)
            ret^=d[i];
    return ret;
}

最小值

最小值即为最低位上的线性基

long long query_min()
{
    for (int i=0;i<=60;i++)
        if (d[i])
            return d[i];
    return 0;
}

k小值

根据性质3。
我们要将线性基改造成每一位相互独立。
具体操作就是如果i<j,aj的第i位是1,就将aj异或上ai。
经过一系列操作之后,对于二进制的某一位i。只有ai的这一位是1,其他都是0。
所以查询的时候将k二进制拆分,对于1的位,就异或上对应的线性基。
最终得出的答案就是k小值。

void rebuild()
{
    for (int i=60;i>=0;i--)
        for (int j=i-1;j>=0;j--)
            if (d[i]&(1LL<<j))
                d[i]^=d[j];
    for (int i=0;i<=60;i++)
        if (d[i])
            p[cnt++]=d[i];// 存放起来
}
long long kthquery(long long k)
{
    long long ret=0;
    if (k>=(1LL<<cnt))
        return -1;
    for (int i=60;i>=0;i--)
        if (k&(1LL<<i))
            ret^=p[i];
    return ret;
}

模板

struct L_B{
    long long d[61],p[61];
    int cnt;
    L_B()
    {
        memset(d,0,sizeof(d));
        memset(p,0,sizeof(p));
        cnt=0;
    }
    bool insert(long long val)
    {
        for (int i=60;i>=0;i--)
            if (val&(1LL<<i))
            {
                if (!d[i])
                {
                    d[i]=val;
                    break;
                }
                val^=d[i];
            }
        return val>0;
    }
    long long query_max()
    {
        long long ret=0;
        for (int i=60;i>=0;i--)
            if ((ret^d[i])>ret)
                ret^=d[i];
        return ret;
    }
    long long query_min()
    {
        for (int i=0;i<=60;i++)
            if (d[i])
                return d[i];
        return 0;
    }
    void rebuild()
    {
        for (int i=60;i>=0;i--)
            for (int j=i-1;j>=0;j--)
                if (d[i]&(1LL<<j))
                    d[i]^=d[j];
        for (int i=0;i<=60;i++)
            if (d[i])
                p[cnt++]=d[i];
    }
    long long kthquery(long long k)
    {
        long long ret=0;
        if (k>=(1LL<<cnt))
            return -1;
        for (int i=60;i>=0;i--)
            if (k&(1LL<<i))
                ret^=p[i];
        return ret;
    }
    int check(long long x)
    {
        for(long long i=60;i>=0;i--)
        {
            if(x&(1LL<<i))
            {
                if(!d[i])
                    return 0;
                x^=d[i];
            }
        }
        return 1;
    }
}LB;// a[maxn]
L_B merge(const L_B &n1,const L_B &n2) //合并 a[nx]=merge(a[nx],a[ny]);
{
    L_B ret=n1;
    for (int i=60;i>=0;i--)
        if (n2.d[i])
            ret.insert(n2.d[i]);
    return ret;
}

L_B Merge(L_B A, L_B B) //求交
{
	L_B All, C, D;
	for(int i = 32; i >= 0; i--) {
		All.d[i] = A.d[i];
		D.d[i] = 1ll << i;
	}
	for(int i = 32; i >= 0; i--) 
		if(B.d[i]) {
			ll v = B.d[i], k = 0;
			bool can = true;
			for(int j = 32; j >= 0; j--) 
				if(v & (1ll << j)) {
					if(All.d[j]) {
						v ^= All.d[j];
						k ^= D.d[j];
					}
					else {
						can = false;
						All.d[j] = v;
						D.d[j] = k;
						break;
					}
				}
 
			if(can) {
				ll v = 0;
				for(int j = 32; j >= 0; j--) 
					if(k & (1ll << j)) 
						v ^= A.d[j];
				C.insert(v);
			}
		}
	C.rebuild();
	return C;
}

 

1.求区间任意数组合异或的最大最小值,求区间任意组合的异或值是否有k这个值: 

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAX = 5e5+10;
int a[MAX];
int p[MAX][31],pos[MAX][31];
int n,m;
void add(int x,int id){//构造线性基1 
	int cao=id;
	for (int i = 0; i <= 29;i++){
		p[id][i]=p[id-1][i];
		pos[id][i]=pos[id-1][i];
	}
	for (int i = 29; i >= 0;i--){
		if(x&(1<<i)){
			if(!p[id][i]){
				p[id][i]=x;
				pos[id][i]=cao;
				return;
			}
			if(pos[id][i]<cao){
				swap(p[id][i],x);
				swap(pos[id][i],cao);
			}
			x^=p[id][i];
		}
	}
}
int queryMAX(int l,int r){//查询区间任意组合最大值 
	int maxx=0;
	for (int i = 29; i >= 0;i--){
		if(pos[r][i]>=l&&(maxx^p[r][i])>maxx){
			maxx^=p[r][i];
		}	
	}
	return maxx;
}
int queryMIN(int l,int r){//查询区间任意组合最小值 
	for (int i = 0; i <= 29;i++){
		if(pos[r][i]>=l&&p[r][i]){
			return p[r][i];
		}	
	}
	return 0;
}
bool querycunzai(int l,int r,int k){//查询区间任意组合的值是否有k 
	for (int i = 29; i >= 0;i--){
		if(pos[r][i]>=l&&(k>>i)){
			k^=p[r][i];
		}	
	}
	return k==0;
}
int main(){
	scanf("%d",&n);
	for (int i = 1; i <= n;i++) {
		scanf("%d",&a[i]);
		add(a[i],i);
	}
	scanf("%d",&m);
	while(m--){
		int l,r;
		scanf("%d%d",&l,&r);
//		int ans1=queryMAX(l,r);
//		printf("%d\n",ans1);
//		int ans2=queryMIN(l,r);
//		printf("%d\n",ans2);
		int k;
		scanf("%d",&k);
		bool ans3=querycunzai(l,r,k);
		printf(ans3? "yes\n":"no\n");
	}
	return 0;
} 

 

2.求n个数中任意数异或组成的第k小的数:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAX = 5e5+10;
int a[MAX];
int p[31];
int n,m,cnt;
void add(int x,int id){//构造线性基 
	for (int i = 29; i >= 0;i--){
		if(x&(1<<i)){
			if(!p[i]){
				p[i]=x;
				return;
			}
			x^=p[i];
		}
	}
}
void rebuild() {//重构线性基 (先构造,在构造完成的基础上再重构)
	cnt=0;
	for(int i = 29; i >= 0;i--){
		for(int j = i-1; j >= 0;j--){
			if((p[i]>>j)&1) p[i]^=p[j];
		}	
	}	
	for(int i = 0; i <= 29;i++){
		if(p[i]) p[cnt++]=p[i];
	}
}
int querykth(int k) {//查询第k小的异或数 
	if (n!=cnt) k--;
	int ans=0;
	if(k>=(1<<cnt)) return -1;
	for(int i = 29; i >= 0;i--){
		if((k>>i)&1) ans^=p[i];
	}
	return ans;
}
int main(){
	scanf("%d",&n);
	for (int i = 1; i <= n;i++) {
		scanf("%d",&a[i]);
		add(a[i],i);
	}
	rebuild();
	scanf("%d",&m);
	while(m--){
		int k;
		scanf("%d",&k);
		int ans=querykth(k);
		printf("%d\n",ans);
	}
	return 0;
} 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值