zoukankan      html  css  js  c++  java
  • 线性基

    定义

    对于一个数集(A),它的线性基是一个最小的数集(B),使得(A)中任意一个数可以通过(B)中的一些数异或得到。

    性质

    • 定义
    • 线性基的异或集合中不会出现0
    • 线性基中每个数最高位不同
    • 线性基中数互相异或不会改变异或集合

    求法

    下面的求法都基于这样一个性质:
    对于一个数集中的两个数(a)(b),将其中一个异或另一个不会改变这个数集的异或集合,即令(a=a xor b)不改变异或集合。
    这个性质是显然的,可以通过再次异或将(a)变回原来的数,相当与没有改动。

    高斯消元

    把每个数拆成二进制位放到矩阵中,对于一个数的一位是1,就用他异或其他所有的这一位为1的数,使得这一位只有这个数为1,从而求得线性基。
    复杂度为:(O(len^2n))(len)是数的二进制长度。

    贪心方法

    求一个线性基相当于不断把数插入已有的线性基,那么考虑插入。从最高位开始,对于这个数的每一位,若这个位在线性基中没有出现过,那么把数插入线性基的这一位。否则,把这个数异或线性基中的这一位,保证下面考虑低位的时候高位没有数。最后要么这个数变为0,要么被插入了线性基中。这样就可以在(O(len))的时间把一个数插入线性基,于是可以在(O(nlen))的时间内完成数集的求线性基。
    求完线性基之后一定要记得(O(len^2))把前面的有这一位的数的异或一下,因为有可能前面插入的数为53,后面的是20,这样会发现53^20=33,还变小了,所以我们要把53替换成33,使得线性基满足性质。
    两个线性基的合并就可以暴力插入合并,启发式合并的复杂度为(O(len*nlogn)).

    用途

    线性基的用途与定义和性质相关。

    异或和第k小

    线性基的最大值很明显就是线性基中所有值异或起来,最小值就是其中最小的那个数。
    对于第k小,由于线性基有每一个最高位都只有一个数有,那么我们按这个最高位从低到高排起来,那么可以发现,如果线性基的长度为(h),那么就有(2^h)种取法,所以对应地把k二进制分解,把答案异或上对应的线性基中的数即可。注意,不是最高位是第几位排第几,而是按最高位从小到大排,中间空出来的直接排进去。
    这里有一个0的问题,即原数集能否异或出0呢?我们无法通过线性基中的数判断,但可以根据线性基的大小判断。如果线性基的大小与原数集大小相同,那么无法异或出0,否则可以。这个可以通过线性基的插入证明,即如果异或出0则不插入,导致线性基的大小小于原数集的大小。

    代码

    这里一定要记得,在插入一个数的时候,先用比他小的消他,再用他消比他大的,不可交换。否则会出现把比他大的数变小了,自己也变小了之类的奇怪情况。

    #include<cstdio>
    #include<cctype>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    typedef long long giant;
    giant read() {
    	giant x=0,f=1;
    	char c=getchar();
    	for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
    	for (;isdigit(c);c=getchar()) x=x*10+c-'0';
    	return x*f;
    }
    const giant maxn=1e4+10;
    const giant maxj=62;
    giant a[maxj],size,b[maxj];
    void insert(giant x) {
    	for (giant j=maxj-1;j>=0;--j) if (x&(1ll<<j)) {
    		if (!a[j]) {
    			for (int i=j-1;i>=0;--i) if (x&(1ll<<i)) x^=a[i];
    			for (int i=j+1;i<maxj;++i) if (a[i]&(1ll<<j)) a[i]^=x;
    			a[j]=x;
    			++size;
    			break;
    		} else x^=a[j];
    	}
    }
    int main() {
    	#ifndef ONLINE_JUDGE
    		freopen("test.in","r",stdin);
    	#endif
    	giant T=read();
    	for (giant tt=1;tt<=T;++tt) {
    		printf("Case #%lld:
    ",tt);
    		memset(a,0,sizeof a),memset(b,0,sizeof b),size=0;
    		giant l=0,n=read();
    		for (giant i=1;i<=n;++i) insert(read());
    		//for (giant i=0;i<maxj;++i) if (a[i]) for (giant j=i+1;j<maxj;++j) if (a[j]&(1ll<<i)) a[j]^=a[i];
    		for (giant i=0;i<maxj;++i) if (a[i]) b[l++]=a[i];
    		giant m=read();
    		while (m--) {
    			giant k=read(),ans=0;
    			if (size<n) --k;
    			if (k==0) puts("0"); else if (k>=(1ll<<size)) puts("-1"); else {
    				for (giant j=0;j<size;++j) if (k&(1ll<<j)) {
    					if ((ans^b[j])>ans) ans^=b[j];
    				}
    				printf("%lld
    ",ans);
    			}
    		}
    	}
    }
    

    能否得到一个数

    问题是能否在原数集中异或出一个给定的数。
    直接把给定的数从高位到低位,如果一位上为1就异或对应的线性基中的数,若最后得到0,那么可行。这里利用了线性基的定义。

    线性基作为一个工具,在很多跟异或有关的题目中都有广泛的应用。

  • 相关阅读:
    Qt之镜像旋转
    Qt之QCheckBox
    Qt之动画框架
    Qt之QFileSystemWatcher
    Qt之qSetMessagePattern
    Qt之qInstallMessageHandler(重定向至文件)
    Qt之qInstallMessageHandler(输出详细日志)
    Qt之窗体透明
    Qt之窗体拖拽、自适应分辨率、自适应大小
    Qt之设置应用程序图标
  • 原文地址:https://www.cnblogs.com/owenyu/p/6724714.html
Copyright © 2011-2022 走看看