线性基概述

看到一篇非常好的博客:传送门!!!
看了以后大概就学会线性基了,向大佬%%%。
蒟蒻再根据自己的理解讲一讲线性基:
线性基在百度和别人博客上基本上看不懂~
就是用有限的数集合{s}代表无限的数集合{k}。具体的代表方式就是它们能组合出的异或值相同。而有限的数具体是多少呢?最大数二进制下的位数。
对于常见的oi题目,线性基就是62个数的集合。

线性基的性质在于它的第 i i 个数二进制下第 i i 位是1。
所以线性基大概长成这样:
1****
-1***
–1**
—1*
—-1
“-”表示没填,“1”表示这一位是1,“*”表示这一位填1或0。
当然,线性基不一定是满的,有可能某一位会没有,如果是满的,那就可以异或出所有的数。所以基本上不会是满的。

线性基的主要思想是贪心,那么怎么构造线性基呢?
假设我们现在已经有了一组线性基,我们在线性基里判断数 r r 能否被异或出来。如果r的二进制最高位是k,那么找线性基里的第 k k 个数,由于再向下找所有的第 k k 位都为0了,所以要组成 r r 必须异或这个数,于是我们把 r r 异或这个数的答案再在线性基中匹配。反之,若线性基中没有第 k k 个数,那么直接把 r r 变成线性基中第 k k 个数。
有人要问有可能第 k k 个数向上有可能第 k k 位为1,为什么不异或它们呢?很显然,如果异或它们,就会导致更高位为1,而这个1是不能被异或消掉的。

遗憾的是,线性基合并只能暴力把一个线性基加入另一个里。

线性基还有一些性质:

  • 对于一个数 x x ,在线性基中如果可以异或出来,只有一种方案把它异或出来(因为能被异或出的数都没有加进线性基)。
  • 任意一个子集的异或值不为0
  • 每个数二进制的位数互不相同

然后可以看一看开头那篇博客的例题。
贴一下我的代码:

k k 大异或和

注意0的存在

 #include
#include
#include
using namespace std;

int n,q,T,cnt;
long long p[65];
long long rk[65];
long long x,k;

void add(long long a)
{
    for(int i=62;i>=0;i--)
    {
        if(((a>>i)&1)==1)
        {
            if(!p[i]){p[i]=a;break;}
            else a=a^p[i];
        }
        if(a==0) break;
    }
}

int main()
{
    scanf("%d",&T);
    int W=T;
    while(T--)
    {
        memset(p,0,sizeof(p));
        memset(rk,0,sizeof(rk));
        cnt=-1;
        scanf("%d",&n);
        printf("Case #%d:\n",W-T);
        for(int i=1;i<=n;i++)
          scanf("%lld",&x),add(x);
        for(int i=62;i>0;i--)
          for(int j=i-1;j>=0;j--)
            if(((p[i]>>j)&1)==1&&p[j])
              p[i]=p[i]^p[j];
        for(int i=0;i<=62;i++)
          if(p[i])
            rk[++cnt]=p[i];
        scanf("%d",&q);
        for(int i=1;i<=q;i++)
        {
            scanf("%lld",&k);
            long long ans=0;
            if(cnt!=n-1) k--;
            if(k>>cnt+1!=0)
            {
                printf("-1\n");
                continue;
            }
            for(int j=cnt;j>=0;j--)
              if(((k>>j)&1)==1)
                ans=ans^rk[j];
            printf("%lld\n",ans);
        }
    }
 } 

最大XOR和路径

这道题一开始用高斯消元做的,后来发现其实高斯消元就是维护的线性基,就直接贴高斯消元的代码了。

#include
#include
#include
using namespace std;
struct lxy{
    int next,to;
    long long len;
}b[2000005];
int n,m,head[500005],cnt,_cnt;
long long a[500005];
long long dis[500005];
bool vis[500005];
long long ans,pos;

void add(int op,int ed,long long len)
{
    b[++cnt].next=head[op];
    b[cnt].to=ed;
    b[cnt].len=len;
    head[op]=cnt;
}

void dfs(int u)
{
    vis[u]=1;
    for(int i=head[u];i!=-1;i=b[i].next)
    {
        if(vis[b[i].to]==1)
          a[++_cnt]=(dis[b[i].to]^dis[u]^b[i].len);
        else
        {
            dis[b[i].to]=(dis[u]^b[i].len);
            dfs(b[i].to);
        }
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) head[i]=-1;
    for(int i=1;i<=m;i++)
    {
        int x,y;long long z;
        scanf("%d%d%lld",&x,&y,&z);
        if(x!=y) add(x,y,z),add(y,x,z);
    }
    dfs(1);ans=dis[n];
    for(int i=62;i>=0;i--)
    {
        pos=(1LL<int j=1;
        while(((a[j]&pos)==0||vis[j]==1)&&j<=_cnt) j++;
        if(j>_cnt) continue;
        vis[j]=1;
        if((ans&pos)==0) ans=(ans^a[j]);
        for(int k=1;k<=_cnt;k++)
          if(vis[k]==0&&(a[k]&pos)==pos)
            a[k]=(a[k]^a[j]); 
    }
    printf("%lld",ans);
} 

CF某G题

给出一个无向图,求所有不同的三元组(u,v,s)的s之和 表示u到v的路径异或和为s。
这道题很有意思,它利用了异或二进制位上互不干扰的性质。每一位分别统计答案。
我个人的想法(不知道对不对,没打代码)是:
首先各联通块分别处理。把环加入线性基中。在dfs一下,统计有多少点对,假设线性基中有 n n 个数,那么第 i i 位的贡献就是 i2n1 i ∗ 2 n − 1
证明:假设我们按上一道题的方式进行处理后。对于简单路径异或值第 i i 位是1的,不异或上第 i i 个,剩下随便选。对于简单路径异或值第 i i 位是0的,异或上第 i i 个,剩下随便选。

你可能感兴趣的:(数论)