【充满原题的胡策】训练2.26 T1(线性基+讲解)

题目:

这里写图片描述

题解:

其实是线性基的基本问题了。
线性基?下见普及向咯
实际上可以从线性基的定义及性质得知,x2的选取就是线性基的最大值,因为二进制位有1必取,这样做也并不担心x1会变得很小,因为只取一个1,剩下的1单数偶数是固定的,我们起码要保证最大的二进制位要取上啊

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#define LL long long 
using namespace std;
const int N=100005;
const int sz=61;
LL a[N],ans1,ansh,lb[sz+5],ans2;int n;
void dfs(int t,LL x1,LL x2)
{
    if (t>n)
    {
        if (x1+x2>ansh)
        {
            ansh=x1+x2;
            ans1=min(x1,x2);
        }else if (x1+x2==ansh) ans1=min(ans1,min(x1,x2));
        return;
    }
    dfs(t+1,x1^a[t],x2);
    dfs(t+1,x1,x2^a[t]);
}
int main()
{
    freopen("divide.in","r",stdin);
    freopen("divide.out","w",stdout); 
    LL sum=0;
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%lld",&a[i]),sum^=a[i];
    if (n<=10) 
    {
        dfs(1,0,0);
        printf("%lld %lld",ans1,ansh-ans1);
    }
    else
    {
        for (int i=1;i<=n;i++)
        {
            LL t=a[i];
            for (int j=sz;j>=1;j--)
              if ((t>>j-1)&1)
              {
                if (!lb[j]){lb[j]=t;break;}
                else t^=lb[j];
              }   
        }
        for (int i=sz;i;i--)
          if ((ans2^lb[i])>ans2) ans2^=lb[i]; 
        printf("%lld %lld",sum^ans2,ans2);
    }
}

普及向:线性基

定义

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

性质

1.设线性基的异或集合中不存在0。
2.线性基的异或集合中每个元素的异或方案唯一,其实这个跟性质1是等价的。
3.线性基二进制最高位互不相同。
4.如果线性基是满的,它的异或集合为[1,2n−1]。
5.线性基中元素互相异或,异或集合不变。

维护

插入

如果向线性基中插入数x,从高位到低位扫描它为1的二进制位。
扫描到第i时,如果ai不存在,就令ai=x,否则x=x⊗ai。
x的结局是,要么被扔进线性基,要么经过一系列操作过后,变成了0。

for (int i=1;i<=n;i++)
        {
            LL t=a[i];
            for (int j=sz;j>=1;j--)
              if ((t>>j-1)&1)
              {
                if (!lb[j]){lb[j]=t;break;}
                else t^=lb[j];
              }   
        }
合并

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

查询

存在性

如果要查询x是否存于异或集合中。
从高位到低位扫描x的为1的二进制位。
扫描到第i位的时候x=x⊗ai
如果中途x变为了0,那么表示x存于线性基的异或集合中。
过程就像添加失败的x一样啊

最大值

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

for (int i=sz;i;i--)
  if ((ans2^lb[i])>ans2) ans2^=lb[i]; 
最小值

最小值即为最低位上的线性基。

long long query_min()
{
    for (int i=0;i<=sz;i++)
        if (lb[i]) return lb[i];
}
k小值

根据性质3。
我们要将线性基改造成每一位相互独立。
具体操作就是如果i < j,aj的第i位是1,就将aj异或上ai。
经过一系列操作之后,对于二进制的某一位i。只有ai的这一位是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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值