线性基

2 篇文章 0 订阅
2 篇文章 0 订阅

前言

线性基,通常用来解决这样一类问题,给你若干个数,让你从这些数的子集的异或和最值或者其它位运算相关操作
这种问题方面,线性基的性能显得非常优越,空间复杂度一般都是 O ( l o g 值 域 ) O(log值域) O(log的,然后各种常见操作的复杂度在 O ( l o g 值 域 ) O(log值域) O(log O ( l o g 2 值 域 ) O(log^2值域) O(log2之间
最重要的是,线性基很好理解、很好写!

实现方式介绍

就拿做前面那个问题来说吧


首先,讲几个前置知识:
对于一个大小为n的集合

前置知识1

取它的子集的异或和的取值是有限的(解释:显然枚举所有子集就好,复杂度 O ( 2 n ) O(2^n) O2n

前置知识2

让其中一个数a异或上这个集合的另一个数b变成c,在这个集合里删除a添加c,它的子集的异或和的取值种类不变(解释:如果想要获得原来的a的效果,只需要加入c并且把b是否选择取反即可)


一个线性基里面有 O ( l o g 值 域 ) O(log值域) O(log)个位置,第i个位置的元素的最高位是 2 i 2^i 2i,根据这个性质就可以成功推出线性基操作的具体实现方式

定义一个线性基

只要开一个O(log值域)的数组就好了

struct Linear_Basis
{
	int a[maxn];
	Linear_Basis(){memset(a,0,sizeof(a));}
}
插入操作

对于一个线性基,现在要插入一个数,这个时候就从这个数的高位开始比对,如果相应的线性基是空的,那么就加进去(然后break),否则就异或上线性基里的数,来去除这个最高位上的值(由前置知识2可知正确性不变),那么如果到最后变成0了,那么说明这个数插入之后对线性基没有任何影响,否则说明有,复杂度 O ( l o g 值 域 ) O(log值域) Olog

inline bool insert(register int val)
{
	for(register int i=maxn-1;i>=0;i--)
		if(val&bit[i])
		{
			if(!a[i])
			{
		        a[i]=val;
			    break;
		    }
		    val^=a[i];
	    }
	return val>0;
}
询问最大值操作

从高位往低位贪心,如果异或上这个数能够让答案变大那就异或,这么做一定优(如果能让这一位变成1那么就比低的位怎么变都要有用),复杂度 O ( l o g 值 域 ) O(log值域) Olog

inline int query_max()const
{
	register int res=0;
	for(register int i=maxn-1;i>=0;i--)if((res^a[i])>res)res^=a[i];
	return res;
}
询问不为0的最小值操作

从低位到高位,第一个有值的就是答案,复杂度 O ( l o g 值 域 ) O(log值域) Olog

inline int query_min()const
{
	for(register int i=0;i<maxn;i++)if(a[i])return a[i];
	return 0;
}
合并操作

把线性基里的每一个元素都提出来,加入另一个线性基,复杂度 O ( l o g 2 值 域 ) O(log^2值域) Olog2

inline void merge(const Linear_Basis &b)
{
	for(register int i=0;i<maxn;i++)if(b.a[i])insert(b.a[i]);
}
化简线性基

枚举一个元素,把能消掉的位都化简掉,便于之后的第k小值的操作,并且提取出有值的位,复杂度 O ( l o g 2 值 域 ) O(log^2值域) Olog2

inline void rebuild()
{
	for(rg int i=maxn-1;i>=0;i--)
	    for(rg int j=i-1;j>=0;j--)
	        if(a[i]&bit[j])
	            a[i]^=a[j];
	cnt=0;
	for(rg int i=0;i<maxn;i++)
	    if(a[i])
	        p[cnt++]=a[i];
}
询问第k小操作

先化简一遍,预处理复杂度 O ( l o g 2 值 域 ) O(log^2值域) Olog2,单次询问只需要按进制异或起来即可,复杂度为 O ( l o g 值 域 ) O(log值域) Olog

inline int kthquery(const int k)const
{
	rg int res=0;
	if(k>=bit[cnt])return -1;
	for(rg int i=maxn-1;i>=0;i--)
	    if(k&bit[i])
	        res^=p[i];
	return res;
}

整合代码

#include<cstdio>
#include<cctype>
#include<cstring>
#include<ctime>
namespace fast_IO
{
    const int IN_LEN=10000000,OUT_LEN=10000000;
    char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf,*lastin=ibuf+IN_LEN,*lastout=obuf+OUT_LEN-1;
    inline char getchar_(){return (ih==lastin)&&(lastin=(ih=ibuf)+fread(ibuf,1,IN_LEN,stdin),ih==lastin)?EOF:*ih++;}
    inline void putchar_(const char x){if(oh==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;*oh++=x;}
    inline void flush(){fwrite(obuf,1,oh-obuf,stdout);}
}
using namespace fast_IO;
//#define getchar() getchar_()
//#define putchar(x) putchar_((x))
typedef long long LL;
#define int LL
#define rg register
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(a%b==0)return b;return gcd(b,a%b);}
template <typename T> inline T square(const T x){return x*x;};
template <typename T> inline void read(T&x)
{
    char cu=getchar();x=0;bool fla=0;
    while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
    while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
    if(fla)x=-x;  
}
template <typename T> void printe(const T x)
{
    if(x>=10)printe(x/10);
    putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
    if(x<0)putchar('-'),printe(-x);
    else printe(x);
}
const int maxn=51;
int bit[maxn];
inline void init()
{
	bit[0]=1;for(rg int i=1;i<maxn;i++)bit[i]=bit[i-1]<<1;
}
int n,p[maxn],cnt;
struct Linear_Basis
{
	int a[maxn];
	Linear_Basis(){memset(a,0,sizeof(a));}
	inline bool insert(int val)
	{
	    for(rg int i=maxn-1;i>=0;i--)
	        if(val&bit[i])
	        {
	            if(!a[i])
	            {
	            	a[i]=val;
	            	break;
	        	}
	        	val^=a[i];
	    	}
	    return val>0;
	}
	inline void merge(const Linear_Basis &b)
	{
	    for(rg int i=0;i<maxn;i++)if(b.a[i])insert(b.a[i]);
	}
	inline int query_max()const
	{
	    rg int res=0;
	    for(rg int i=maxn-1;i>=0;i--)if((res^a[i])>res)res^=a[i];
	    return res;
	}
	inline int query_min()const
	{
	    for(rg int i=0;i<maxn;i++)if(a[i])return a[i];
	    return 0;
	}
	inline void rebuild()
	{
	    for(rg int i=maxn-1;i>=0;i--)
	        for(rg int j=i-1;j>=0;j--)
	            if(a[i]&bit[j])
	                a[i]^=a[j];
	    cnt=0;
	    for(rg int i=0;i<maxn;i++)
	        if(a[i])
	            p[cnt++]=a[i];
	}
	inline int kthquery(const int k)const
	{
	    rg int res=0;
	    if(k>=bit[cnt])return -1;
		for(rg int i=maxn-1;i>=0;i--)
	    	if(k&bit[i])
	        	res^=p[i];
		return res;
	}
}Q;
signed main()
{
	init();
	read(n); 
	for(rg int i=1;i<=n;i++)
	{
		int x;read(x);
		Q.insert(x);
	}
	print(Q.query_max());
	return flush(),0;
}

模板题

洛谷P3812 【模板】线性基

经典应用(update by 2018/12/28)

给出一个包含 n n n m m m位二进制数的可重集合,求有多少个子集满足所有数的异或值为 0 0 0
这是一个非常经典的问题,做起来很简单,只要把每个数插到线性基里,记最后线性基里的元素为 x x x,那么最后答案为 2 n − x 2^{n-x} 2nx
复杂度 Θ ( n m ⌈ m 64 ⌉ ) \Theta(nm\left\lceil\frac m{64}\right\rceil) Θ(nm64m),非常优越
当然,如果 n n n比较小的话,复杂度可以变为 Θ ( n m ⌈ n 64 ⌉ ) \Theta(nm\left\lceil\frac n{64}\right\rceil) Θ(nm64n)
有一种很好的写法是:对于一位,根据所有数在这一位上是否为 1 1 1来构造一个长度为 n n n 01 01 01串,把它放在长度为 n n n的线性基里就好了
(转化的正确性证明:把所有的 n n n m m m 01 01 01串看成矩阵,那么线性基中的元素数量等于矩阵秩的数量,矩阵转置后秩的数量不变,所以可以这么做)
证明:若一个数能加入线性基中,说明这个数如果被选择,那么将没有办法通过已有的数将其的贡献抵消,无贡献;若一个数不能加入线性基中,那么我们可以选入这个数,并且翻转线性基中对应位的选择状态,那么选或不选,方案数乘 2 2 2
例题:hdu3915
代码

#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#define rg register
typedef long long LL;
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline void mind(T&a,const T b){a=a<b?a:b;}
template <typename T> inline void maxd(T&a,const T b){a=a>b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
template <typename T> inline T lcm(const T a,const T b){return a/gcd(a,b)*b;}
template <typename T> inline T square(const T x){return x*x;};
template <typename T> inline void read(T&x)
{
    char cu=getchar();x=0;bool fla=0;
    while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
    while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
    if(fla)x=-x;
}
template <typename T> inline void printe(const T x)
{
    if(x>=10)printe(x/10);
    putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
    if(x<0)putchar('-'),printe(-x);
    else printe(x);
}
const int mod=1000007;
int T,N,bit[101];
int base[31],sum;
int main()
{
	bit[0]=1;
	for(rg int i=1;i<=100;i++)bit[i]=(bit[i-1]<<1)%mod;
	read(T);
	while(T--)
	{
		memset(base,0,sizeof(base)),sum=0;
		read(N);
		for(rg int i=1;i<=N;i++)
		{
			int x;read(x);
			for(rg int j=30;j>=0;j--)
				if((x&(1<<j)))
				{
					if(base[j])x^=base[j];
					else
					{
						sum++;
						base[j]=x;
						break;
					}
				}
		}
		print(bit[N-sum]),putchar('\n');
	}
	return 0;
}

总结

在使用线性基的过程中,要看好数据范围,注意longlong的问题。然后,线性基也是一个很灵活的东西,可以自己更改具体实现的方式来增强功能,然后就可以做出各种题目啦。当然线性基使用范围不算广,所以一般比赛里碰到的话最多是作为算法的一个部分,但是思维方式非常妙,可以好好研究一下

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
线性可以用来判断原集合是否封闭。如果一个元素能够被线性向量线性表示,那么它就可以由原集合中的元素经过线性组合得到,即原集合是封闭的。否则,如果有一个元素不能被线性向量线性表示,那么它就无法由原集合中的元素经过线性组合得到,即原集合不是封闭的。 具体地,我们可以通过将待判断的元素与线性向量进行异或操作来判断是否能够线性表示。如果待判断元素与线性向量进行异或操作后得到零向量,则说明待判断元素可以由线性向量线性表示。如果待判断元素与线性向量进行异或操作后得到非零向量,则说明待判断元素无法由线性向量线性表示。 因此,我们可以通过判断待判断元素与线性向量进行异或操作的结果是否为零向量来判断原集合是否封闭。如果待判断元素与线性向量进行异或操作后都得到零向量,则原集合是封闭的;否则,原集合不是封闭的。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [线性模板](https://blog.csdn.net/weixin_43519854/article/details/96977900)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [【矩阵论】线性空间与线性变换(3)(4)](https://blog.csdn.net/kodoshinichi/article/details/108916238)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值