数论 —— 线性基小结+模板

线性基

线性基可以理解为 原集合的一个子集,但是可以 线性表示原集合中所有的元素。(按线性代数中来说就是一个最大无关组,或者说基础解系)

ACM中的线性基一般就用于 求异或和的最值(任意选取元素使得异或和最大),线性基里集合的异或组合能表示出原序列的异或组合。(异或组合只能 *1、*0、异或)

关于线性基在线性代数中的定义和证明挺麻烦…就不管了 (因为懒 )

异或运算是按二进制位运算(可以理解为按位相加不进位),所以为了让线性基能够表示原序列,那么应当 按位进行构建,同时线性基中的 元素是原序列的异或组合



线性基的构建

原序列 a: a[1], a[2], … a[n]
线性基 d[max_base]: d[i]是最高位为第i+1位(即第i+1位一定为1)的一个原序列的异或组合

线性基写成类似矩阵的形式正好是一个上三角矩阵,例如 当max_base=6
1***** d[5]
01**** d[4]
001*** d[3]
0001** d[2]
00001* d[1]
000001 d[0]

这样看可以发现线性基的构建就是对排好的原序列进行高斯消元。

可以发现若线性基填满了,该线性基甚至能表示 0 ~ 2max_base 的所有数(即能控制所有二进制位的0、1)

int a[maxn],d[max_base];
void ins(int x)
{
    //从高位到低位进行插入
    for(int i=max_base;i>=0;i--)
    {
        if(x&(1<<i))    //若第i+1位为1 (由于从高位开始遍历,所以第i+1位即x的最高位)
        //若为LL,则要改为 (x&(1LL<<i))
        {
            if(!d[i])    //若该位还无元素
            {
                d[i]=x;  //则放入
                break;
            }
            else
                x^=d[i]  //让x的第i+1位变为0,同时得到x的一个新异或组合
        }
    }
}
void built()
{
    memset(d,0,sizeof(d));    //全部置零
    for(int i=1;i<=n;i++)
        ins(a[i]);
}


线性基的基本应用

主要是利用贪心的思想去应用求得的线性基d[i](线性基可以更好地可以表示原序列),若d[i] ≠ 0,则d[i]的第 i+1 位一定为1(且是最高位)


①异或和最大值

从高位到低位贪心选取,很好理解,因为d[i]的第i+1位是1,高位为1权值更大。

int query_max()
{
    int ans=0;   //若是求和 num 异或的最大值,令 ans = num即可
    for(int i=max_base;i>=0;i--)
    {
        if(ans^d[i]>ans)    //若可以更大,则选用
            ans=ans^d[i];   //也可以直接写成 ans=max(ans, ans^d[i])
    }
    return ans;
}

②异或和最小值

明显就是最小的那个d[i],即从低位到高位,找到第一个非空的d[i]

注意:如果 非空d[i]个数 < 原序列总数n,异或和 最小值是0

int query_min()
{
	if(cnt<n)   //非空d[i]个数 < 原序列总数n
		return 0;
    for(int i=0;i<=max_base;i++)
    {
        if(!d[i])
            return d[i];
    }
}

③异或判特值(num能否由原序列异或得到)

异或是二进制位相同得到0,相异得到1,所以:

要证明 x = num (x即原序列的异或组合)
即证 x ^ num = 0

所以只要贪心处理,线性基与num从高位到低位异或,尽量让num减小。

注意:num本身就是0,那么只有当 非空 d[i] 个数 < 原序列总数n, 才可以表示。

bool query_num(int num)
{
	if(num==0)   //若num为0
	{
		if(cnt<n)
			return true;
		else
			return false;
	}
    for(int i=max_base;i>=0;i--)
    {
        if(num^d[i]<num)
            num=num^d[i];
    }
    //其实num最后得到的值也正是num和原序列异或的最小值
    if(num==0)
        return true;
    else
        return false;
}

④异或和的个数(原序列有多少个不同的(非零)异或和)

由于求得的线性基中的元素是线性无关的(独立的),那么其能组合出的异或和的个数就是答案
2 非 空 d [ i ] 的 个 数 − 1 2^{非空d[i]的个数}-1 2d[i]1
( 1<<非空d[i]的个数 ) - 1 ( 减1 是因为不能一个都不选)

注意:非空 d[i] 个数 < 原序列总数n,则说明还可以表示 0,个数应当+1


⑤第k小的异或和(HDU-3949

先对原线性d基进行重建,对于d[i],除第i+1位(即最高位)外尽可能化为0(有点像矩阵化为标准型)

然后对k进行二进制拆分,若第1位为1,则异或第1个非空d[],… ,若第 i 位为1,则异或第 i 个非空d[]。(d[]按其下标递增编号)

void rebuilt()    //重构线性基
{
    for(int i=max_base;i>=0;i--)
    {
        for(int j=i-1;j>=0;j--)
        {
            if(d[i]&(1LL<<j))   //利用之后的d[j]来清零
                d[i]^=d[j];
        }
    }
    cnt=0;       //非零d[]个数
    for(int i=0;i<=max_base;i++)
    {
        if(d[i])
            D[cnt++]=d[i];
    }
}
LL query_kth(LL k)
{
    LL ans=0;
    if(cnt<n)    //cnt<N说明原序列可异或得到0,则k--
        k--;     //因为这里的方法线性基异或组合无法得到0
    if(k>(1LL<<cnt)-1)  //若 k>(非零)异或和总数,说明不存在第k小
        return -1;
    for(int i=max_base;i>=0;i--)
    {
        if(k&(1LL<<i))
            ans^=D[i];
    }
    return ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值