关于线性基的学习与理解 性质

学习来自:https://www.cnblogs.com/ljh2000-jump/p/5869991.html

和 https://blog.csdn.net/a_forever_dream/article/details/83654397

此处只拿出他们做题总结的部分,原理过程删掉了

1、线性基:

  若干数的线性基是一组数a1,a2,...an,其中ax的最高位的1在第x位。

  通过线性基中元素xor出的数的值域与原来的数xor出数的值域相同。

2、线性基的构造法:

  对每一个数pp从高位到低位扫,扫到第x位为1时,若ax不存在,则ax=pax=p并结束此数的扫描,否则令p=p   xor ax。

3、查询:

  用线性基求这组数xor出的最大值:从高往低扫ax,若异或上ax使答案变大,则异或。

4、判断:

  用线性基求一个数能否被xor出:从高到低,对该数每个是1的位置x,将这个数异或上axax(注意异或后这个数为1的位置和原数就不一样了),若最终变为0,则可被异或出。当然需要特判00(在构造过程中看是否有p变为0即可)。例子:(11111,10001)(11111,10001)的线性基是a5=11111,a4=01110,要判断11111能否被xor出,11111 xora5=0,则这个数后来就没有是1的位置了,最终得到结果为00,说明11111能被xor出。

 

关于线性基的模板:

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define mem(a,x) memset(a,x,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll a[N],p[100];
int n;
int insert(ll x){
	
	for(int j=62;j>=0;j--) {
		if(!(x>>j&1)) continue; 
	 	if(!p[j]) { p[j]=x; return 1; }                 
			x^=p[j];
	}
	return 0;
}

一些性质:

你可以理解为将一个序列处理完之后得到的产物,并且有如下性质(后面有证明):

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

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

分类讨论一下


1、不能成功插入线性基**


       什么时候不能插入进去呢?
       显然就是它在尝试插入时异或若干个数之后变成了0 00。
       那么就有如下式子:
       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
       这个性质被BJWC拿来出过一道题,那题网上几乎找不到证明(都是草草的给出做法然后贴代码),然而如果你熟记这个性质3,那么很快就能想明白那题。(这题在文章末尾会给出)
       那么我来尝试证明性质3(毕竟网上几乎找不到证明作为参考,这里笔者我只能乱推了):

还是没什么卵用地分类讨论一下


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


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

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


       我们设x不能插入到线性基里面,那么一定满足形如d[a] d[a]d[a] ^ d[b] d[b]d[b] ^ d[c]=x d[c]=xd[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] d[c]d[c]插入不进去,也就是说,对于插入不进去的元素,改变插入顺序后,要么还是插入不进去,要么就是插入进去了,同时另一个原来插入的进去的元素插入不进去了,所以,可以插入进去的元素数量一定是固定的。
       显然,如果你去掉线性基里面的任意一个数,都会使得原序列里的一些(或一个)数无法通过用线性基里的元素异或得到,所以,每一个元素都是必要的,换句话说,这里面没有多余的元素,所以,这个线性基的元素个数在保持性质1的前提下,一定是最少的。
       顺便贴上插入的代码(代码里的ll 都是指longlong):

void add(ll x)
{
    for(int i=50;i>=0;i--)
    {
        if(x&(1ll<<i))//注意,如果i大于31,前面的1的后面一定要加ll
        {
            if(d[i])x^=d[i];
            else
            {
                d[i]=x;
                break;//记得如果插入成功一定要退出
            }
        }
    }
}


如何求最大值


       完整的说,是如何求在一个序列中,取若干个数,使得它们的异或和最大。
       首先构造出这个序列的线性基,然后从线性基的最高位开始,假如当前的答案异或线性基的这个元素可以变得更大,那么就异或它,答案的初值为0。
       代码如下:

ll ans()
{
    ll anss=0;
    for(int i=50;i>=0;i--)//记得从线性基的最高位开始
    if((anss^d[i])>anss)anss^=d[i];
    return anss;
 }   


如何求最小值


       显然的,最小值一定是最小的d[i]。(如果让最小的d[i]去异或其它的d[i]一定会让它变得更大,所以它自己就是最小的)
 

如何求第k小的值


       完整的说,应该是——从一个序列中取任意个元素进行异或,求能异或出的所有数字中第k小的那个。
       首先,要对这个序列的线性基处理一下,对于每一个d[i] d[i]d[i],枚举j=i to1,如果d[i] (2)的第j位为1,那么d[i]异或d[j−1]。
       那么处理完一个线性基之后,应该大致是长这个样子的(x表示0或1):
       1xxxx0xxx0x
                1xxx0x
                        1x
       求解过程:将k先转成二进制,假如k的第i位为1,ans就异或上线性基中第i ii个元素(注意不是直接异或d[i-1])。
       代码如下:

void work()//处理线性基
{
	for(int i=1;i<=60;i++)
	for(int j=1;j<=i;j++)
	if(d[i]&(1<<(j-1)))d[i]^=d[j-1];
}
ll k_th(ll k)
{
	if(k==1&&tot<n)return 0;//特判一下,假如k=1,并且原来的序列可以异或出0,就要返回0,tot表示线性基中的元素个数,n表示序列长度
	if(tot<n)k--;//类似上面,去掉0的情况,因为线性基中只能异或出不为0的解
	work();
	ll ans=0;
	for(int i=0;i<=60;i++)
	if(d[i]!=0)
	{
		if(k%2==1)ans^=d[i];
		k/=2;
	}
}

如何判断一个数是否能被当前线性基中的元素异或得到

       把它尝试插入进线性基里面去,假如可以插入,说明不能异或得到,假如插不进去,说明能异或得到 (原理然而上面已经讲了)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长沙大学ccsu_deer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值