线性基

线性基

定义

线性基

一个由若干数组成的集合\(S\),定义它的线性基为最小的一个集合\(T\)使得\(S\)\(T\)能够异或出的值域(集合)相同

张成

一个集合\(S\),随意取若干个数异或,能够凑出的数的集合称为集合\(S\)的张成,表示为\(span(S)\)

线性相关

如果一个集合内有某个数满足:去除这个数后的集合可以凑出这个数。那么称集合\(S\)线性相关。否则为线性无关

求法

将每个数看做一个01串,按位操作,从高到低,在没加入线性基的数中找,看有没有当前位为1的,如果有,将其加入线性基中,并用现在加入线性基中的这个数 消去 原本线性基中其他数当前位的1(高斯消元)。如果没有,跳过当前位直接对下一位进行操作

这样消出的线性基满足每一列最多只有一个1,并且从前往后,线性基中每个数的第一个1所在位置依次右移,即前面的数严格大于后面的数。
类似于这样的形式:

\[1001010101\\ 0101010101\\ 0010110101\\ 0000011010\\ .\\ .\\ .\\ \]

证明

考虑这样为什么这样求出的线性基是合法的。(不知道怎么证这样一定是最小的)
由线性基的定义可得,如果\(S\)集合中的每个数,都可以被\(T\)集合表示出,那么这组线性基一定合法,因此我们只需要考虑能否凑出\(S\)集合即可。
对于\(S\)集合中的任意一个数的任意一位,如果这一位无法被凑出,意味着这一位是1,但线性基中却没有这一位是1的数,但如果这样,这个无法被凑出的数就应该在当时被加入线性基中,所以这样的数不存在。

你可能会有这样的疑问:是否可能有一个数因为线性基中某几位被捆绑在一起而无法凑出?
考虑几个位为什么会捆绑在一起?因为一开始加入的某个数在这几位上是1,但后面却没有在这一位上是1的数(消去后)。
一个数如果会因为某几位被捆绑而无法凑出,只可能是实际上是10,但只能凑出11(线性基中只有11,没有1x和01).但这种情况是不可能的,因为当我们将11加入线性基时,由于10的首位是1,所以11会对10进行消数的操作,使得10变为01,这时,第二位是1的数是存在的,所以11不会被捆绑在一起。

性质/用法

性质1

1,假设原来的集合大小为\(|S|\),线性基大小为\(|T|\),对于任意一个数\(x\),如果它可以被线性基中的数表示出来,那么从集合\(S\)中选若干个数\(xor\)\(x\)的方案数为\(2^{|S| - |T|}\)
证明:考虑\(|S| - |T|\)实际上就是集合中没有加入线性基中的数的个数,我们在这个集合中任意选一些数的方案数为\(2^{|S| - |T|}\),而每一种取法异或出来的数,都可以由线性基中的某一个确定的方案凑出。将这2个相同的数异或,可以得到\(0\),因此我们有\(2^{|S| - |T|}\)种凑出\(0\)的方案。
因此对于任意一个数,如果我们可以用线性基凑出,我们可以拿它和凑\(0\)的方案进行异或,所以肯定也有\(2^{|S| - |T|}\)种方案,否则肯定就凑不出了。

用法1

线性基可以判断任意一个数是否可以被集合\(S\)内的数凑出。

从高位开始,依次操作,如果当前位为1,那么异或上线性基中当前位为1的数,否则不操作。最后判断结果是否为0,为0即可以凑出(一个数在异或后变成了0,肯定是因为这个数异或上了它自己)

由此我们可以引申出另一个经典用法:用线性基来求一个集合的最大异或和。
从高位开始贪心,每次检查异或上线性基的当前位是否可以使得答案变大,如果可以,就异或,否则跳过。

求最大异或和的代码:

#include<bits/stdc++.h>
using namespace std;
#define R register int
#define AC 55
#define LL long long

int n, tot;
LL maxn, ans;
LL f[AC];

inline LL read()
{
    LL x = 0;char c = getchar();
    while(c > '9' || c < '0') c = getchar();
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x;
}

void pre()
{
    n = read(), maxn = 1LL << 50;
    for(R i = 1; i <= n; i ++, maxn = 1LL << 50) 
    {
        LL x = read();
        for(R j = 50; ~j; j --, maxn >>= 1)
        {
            if(!(x & maxn)) continue;
            if(!f[j]) {f[j] = x; break;}
            else x ^= f[j];
        }
    }
}

void work()
{
    for(R i = 51; ~i; i --)
        if((ans ^ f[i]) > ans) ans ^= f[i];
    printf("%lld\n", ans);
}

int main()
{
    //freopen("in.in", "r", stdin);
    pre();
    work();
//	fclose(stdin);
    return 0;
}
posted @ 2019-01-31 16:30  ww3113306  阅读(244)  评论(0编辑  收藏  举报
知识共享许可协议
本作品采用知识共享署名-非商业性使用-禁止演绎 3.0 未本地化版本许可协议进行许可。