线性基笔记

参考:czdb ,Yveh ,ouuan ,落英吹 ,Hypoc_的blog

线性基是啥?你可以理解为将一个序列处理完之后得到的产物

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

性质
1.设线性基的异或集合中不存在0 且线性基里面的任意一些数异或起来都不能得到0 

2.线性基的异或集合中每个元素的异或方案唯一,然后原序列里面的任意一个数都可以由线性基里面的一些数异或得到

   并且在保持性质一的前提下,数的个数是最少的其实这个跟性质1是等价的。 
3.线性基二进制最高位互不相同。 
4.如果线性基是满的,它的异或集合为[1,2^n−1]
5.线性基中元素互相异或,异或集合不变。

 

性质 1,2的证明

证明性质1
       我们知道了线性基的构造方法后,其实就可以很容易想到如何证明性质1了,我们设原序列里面有一个数x ,我们尝试用它来构造线性基,那么会有两种结果——1、不能成功插入线性基;2、成功插入线性基。

分类讨论一下
1、不能成功插入线性基**
       什么时候不能插入进去呢?
       显然就是它在尝试插入时异或若干个数之后变成了0 。
       那么就有如下式子:
       x  ^ d[a]  ^ d[b] ^ d[c] ^...=0
       根据上面的那个小性质,则有:
       d[a]  ^ d[b] ^ d[c]  ^...=x 
       所以,如果x 不能成功插入线性基,一定是因为当前线性基里面的一些数异或起来可以等于x 。

2、可以成功插入线性基
       我们假设x插入到了线性基的第i个位置,显然,它在插入前可能异或若干个数,那么就有:
       x  ^ d[a]  ^ d[b]  ^ d[c]  ^ …=d[i] 
       d[i]  ^ d[a]  ^ d[b]  ^ d[c]  ^ …=x
       所以显然,x 此时也可以由线性基里面的若干个数异或得到。

综上,性质1得证
再看性质2
       我们使用反证法
       设d[a]  ^ d[b]  ^ d[c]=0 (其中d[c] 比d[a] 和d[b]要更晚被插入线性基)
       那么有d[a]  ^ d[b]=d[c] 
       因为d[c]可以由d[a] ^ d[b] 得到
       所以d[c]不可能插入线性基

       故假设不成立,所以线性基中不存在有任何数异或起来可以得到0 。
 

最后看性质3

1、假如序列里面的所有元素都可以插入到线性基里面

      显然如果是这种情况的话,不管是用什么顺序将序列里的数插入到线性基里,线性基中的元素一定与原序列元素数量相同。          所以性质3成立。

2、假如序列里面的一些元素不能插入到线性基里面

      我们设x不能插入到线性基里面,那么一定满足形如d[a]^ d[b]^ d[c]=x 的式子,

      那我们尝试将插入顺序改变,变成:d[a] 、d[b] 、x 、d[c] 。

      那么显然,d[c]是不可能插入成功的,简单的证明:
       ∵ d[a] ^ d[b] ^ d[c]=x 
       ∴ d[a] ^ d[b] ^ x=d[c](根据上面那条并没有什么卵用的异或性质)
      原来是x 插入不进去,改变顺序后,d[c] 插入不进去,也就是说,对于插入不进去的元素,改变插入顺序后,要么还是插入不        进去,要么就是插入进去了,同时另一个原来插入的进去的元素插入不进去了,所以,可以插入进去的元素数量一定是固定          的。
      显然,如果你去掉线性基里面的任意一个数,都会使得原序列里的一些(或一个)数无法通过用线性基里的元素异或得到,          所以,每一个元素都是必要的,换句话说,这里面没有多余的元素,所以,这个线性基的元素个数在保持性质1的前提下,一        定是最少的。

插入

// d[j] 表示线性基的第 j 位
void add(ll x)
{
    for(int i=60; ~i; i--)
    {
        if(x&(1ll<<i))//注意,如果i大于31,前面的1的后面一定要加ll
        {
            if(d[i])x^=d[i];
            else
            {
                d[i]=x;
                break;//记得如果插入成功一定要退出
            }
        }
    }
}

合并

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

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存于线性基的异或集合中。

bool check(ll x) {
        for(int i = 63; i >= 0; i--) {
            if(x & (1LL<<i)) x ^= d[i]; 
        }
        return x==0;
    }

判断一个数能否由若干数的子集异或得到

假装你要把它插入到线性空间的元素集合中,看线性基是否需要新增元素即可。

最大子集异或和

求出线性基,依次考虑线性基的每一位,若异或上能让答案更大(即答案的这一位为零)就异或上。因为如果不异或上,这一位就是零,无论后面的位如何,都比这一位为 1 劣。

最大值

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

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小值

线性基中的每个元素的二进制最高位均不同,并且,我们称二进制最高位为第 i位的元素称为“线性基的第 i 位”。

根据性质3。 
我们要将线性基改造成每一位相互独立。 
具体操作就是如果i<j,aj的第i位是1,就将aj异或上ai。 
经过一系列操作之后,对于二进制的某一位ii。只有aiai的这一位是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)
{
    int 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)
    {
        int ret=0;
        if (k>=(1LL<<cnt))
            return -1;
        for (int i=60;i>=0;i--)
            if (k&(1LL<<i))
                ret^=p[i];
        return ret;
    }
}
L_B merge(const L_B &n1,const L_B &n2)
{
    L_B ret=n1;
    for (int i=60;i>=0;i--)
        if (n2.d[i])
            ret.insert(n1.d[i]);
    return ret;
}
struct LinerBase {
    int cnt;
    ll b[65];
    void init() {
        cnt = 0;
        memset(b, 0, sizeof(b));
    }
    bool Insert(ll x) {
        for(int i = 63; i >= 0; i--) {
            if(x & (1LL<<i) ) {
                if(!b[i]) { b[i] = x; cnt++; return true; }
                else x ^= b[i];
            }
        }
        return false;
    }
    bool check(ll x) {
        for(int i = 63; i >= 0; i--) {
            if(x & (1LL<<i)) x ^= b[i]; 
        }
        return x==0;
    }
}B1, B2, B3;


 

发布了183 篇原创文章 · 获赞 31 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/mlm5678/article/details/97611781