线性基讲解

Posted chen-1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线性基讲解相关的知识,希望对你有一定的参考价值。

首先,什么是线性基:

线性基是一个数的集合,任意一个序列都有至少提个线性基。
有一组数a1,a2...an和线性基d1,d2...,dm,di表示**最高位1在第i位的数**。
线性基的作用
由于线性基值域与原数列值域相同的特点,可以用它来维护异或和。
线性基的性质

线性基有以下三大性质:

1.原序列里面的任意一个数都可以由线性基里面的一些数异或得到。
2.线性基里面的任意一些数异或起来都不能得到0。
3.线性基里面的数的个数唯一,并且在保持性质一的前提下,数的个数是最少的。


线性基的插入代码

 

bool add(ll x)
{
    for(int i=30;i>=0;i--)
    {
        if((x>>i)&1)
        {
            if(d[i]) x^=d[i];
            else
            {
                d[i]=x;
                return 1;
            }
        }
    }
    return 0;
}

 

 

 

这样这个d数组的性质就是若d[i]不为0,则(d[i])2的第d[i]的第i+1位为1,并且没有更高位为1了。

性质1的证明

如果有了前面讲的,设原数列有一个数x,我们尝试用它来构造线性基,这样就会有两种可能。
1.不能成功插入线性基。
2.可以成功插入线性基。

接下来分两类讨论性质1的证明

不能成功插入线性基

如果是不能成功插入的话,那么一定是与性质2相悖,所以就可以显然得出这个式子: x^d[a]^d[b]^...=0
则可以推出 d[a]^d[b]^...=x
所以如果x不能插入线性基,一定是前面以及有一些数的异或和为x。满足性质1。

能够成功插入线性基

假设这个数插入到了第i个位置,则可以得出 x^d[a]^d[b]^...=d[i]
能推出 d[i]^d[a]^d[b]^...=x
所以这种情况也能满足性质1。


性质1得证。

性质2的证明

证法显然,但是还是说一下吧。
反证法:假设d[a]^d[b]^...d[i]=0(假设d[i]比d[a]等晚入线性基)
则可以推出d[a]^d[b]^...=d[i],而这样根据入线性基方案,d[i]不可能入线性基,所以假设不成立。

所以性质2得证。

性质3的证明

还是分类讨论

如果序列中所有元素都被插入线性基中

由于所有元素都要入线性基,故不管用什么顺序都是一样的,所以就能保证最优性,即满足性质3.

如果有元素没有被插入线性基中

设这个元素为x,则一定满足一个d[a]^d[b]^d[c]=x的式子,则我们改变一些顺序。(由于a,b,c都是对称的,则只用考虑c即可)
我们可以得到这个式子d[a]^d[b]^x=d[c]
所以d[c] 就无法插入线性基了,所以总数还是一样的,所以改变顺序并不会改变插入数量,所以数量是一定的。

性质3得证。

 

所以来看一道线性基的题目

https://www.luogu.com.cn/problem/P4301

 

 

这道题就是一道经典线性基的题目

解析:这道题怎么做呢?这道题和Nim游戏很像,唯一的区别就是第一次和第二次可以随便取。所以我们只要解决这唯一的不同点也就解决这个问题了。
这道题要让取完之后无论怎么拿都不会出现异或和为0的情况,所以我们正好可以用线性基来维护,但是我们的入线性基的顺序是什么呢?
这里有个贪心,那就是说a^b^c...<=a+b+c+...,这个的证法显然我就不说了。
所以我们根据这个贪心,可以确定是要从大到小的顺序依次考虑入线性基,所以我们一开始就要排个序。
根据题意,如果入线性基成功,那就没有事了,但是如果失败就要统计上ans,所以单独写一个入线性基add的时候要定义的是bool类型的函数,以便判断入线性基是否成功。
最后再考虑-1 的事情,可以构造出一种情况,无论如何先手也不会输,那就是取得只剩1堆,之后先手就直接去完这剩下的一堆即可,所以是不可能输出 -1 的。

 

所以正解代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int NR=105;
int a[NR];
int d[NR];
ll ans;
bool add(ll x)
{
    for(int i=30;i>=0;i--)
    {
        if((x>>i)&1)
        {
            if(d[i]) x^=d[i];
            else
            {
                d[i]=x;
                return 1;
            }
        }
    }
    return 0;
}
bool cmp(int x,int y)
{
    return x>y;
}
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch>9||ch<0){if(ch==-)f=-1;ch=getchar();}
    while(ch<=9&&ch>=0){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    return x*f;
}
int main()
{
//    freopen("1.in","r",stdin);
//    freopen("1.out","w",stdout);
    int n=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++)
    {
        bool flag=add(a[i]);
        if(!flag) ans+=a[i];
    }
    printf("%lld",ans);
    return 0;
}

 

 

以上是关于线性基讲解的主要内容,如果未能解决你的问题,请参考以下文章

应用运筹学基础:线性规划

[算法模板]线性基

如何通过单击片段内的线性布局从片段类开始新活动?下面是我的代码,但这不起作用

bzoj 4269 再见Xor 线性基

Codeforces 1100F(线性基+贪心)

线性基 刷题记录