「线性基」学习小结

向量空间

向量空间亦称线性空间。

形式化的,我们定义一个向量空间\((P,V,+,\cdot)\)

其中 \(P\)是一个域,\(V\)是一个非空的集合,其中的集合称作向量,同时定义两种运算分别为向量加法和标量乘法

一个\(P\)上的向量空间\((P,V,+,\cdot)\),需满足以下8条公理(其中的\(u,v,w\in V\),\(a,b\)是标量):

  1. \(u+(v+w)=(u+v)+w\)
  2. \(u+v=v+u\)
  3. \(\exists\mathbf{0}\)\(s.t. \ \ v+\mathbf{0}=v\)
  4. \(\forall v\in V,\exists w\in V\)\(s.t.\) \(v + w = 0\)
  5. \(a(u+v)=au+av\)
  6. \((a+b)v=av+bv\)
  7. \(a(bv)=(ab) v\)
  8. \(\exists 1,s.t. \ 1 \cdot v=v\)

基(basis)

向量空间\(V\)的基是可以张成\(V\)的一个线性无关的向量组,其中的元素称作基向量。

异或运算&线性基

把一个数的二进制表示看成是一个向量
\[ \mathbf{a}_i=(d_n,...d_0) \]
假设向量\(\mathbf{a}_0,\mathbf{a}_1,...\mathbf{a}_m\),的张成空间是\(S\)

定义一个向量空间\(V=(\{ 0,1\} ,S,xor,\cdot)\)

\(ps:\)这里,我们是把异或当作加法

求法

如何求出\(V\)的一个基\(\mathfrak{B}\)

我们要做的:是扫描每一个向量\(\mathbf{a}_i\),如果它存在于其它向量的张成空间中,就把它去掉

如何判断每个向量能否被前面的向量张成得到?

我们利用高斯消元:

int a[MN],base[log_MN];
inline void calc()
{
    //n is the highest bit
    register int i,j,k;
    for(i=0;i<=m;++i)for(j=n;~j;--j)
        if(a[i]>>j&1)//Scan every bit of a[I] from top to bottom
        {
            if(base[j]) a[i]^=base[j];
            else
            {
                //There's no vector for this bit
                base[j]=a[i];
                /*Maintain a diagonal matrix
                for(k=j-1;~k;--k) 
                    if(base[k]&&(base[j]>>k&1)) base[j]^=base[k];
                for(k=j+1;k<=n;++k) 
                    if(base[k]>>j&1) base[k]^=base[j];
                */
                break;
            }
        }
}

复杂度是\(O(mn)\),若位数较多,通常采用bitset优化,复杂度是\(O(\frac{mn^2}{\omega})\)

可以发现,我们在维护一组向量\(base[]\),满足\(base_i\)的最高位时\(i\)

为了方便,通常只把它消成一个上三角矩阵

当然,你也可以把它消成一个对角线矩阵,满足每一位最多只存在于一个向量中

线性基的运用

在这里,我们不严谨地称一个向量<另一个向量

其实指的是,每个向量对应的数的大小比较

查询最值

贪心求最大值:

  1. 如果所求是对角线矩阵,直接将所有向量相加即可
  2. 如果所求的是上三角矩阵,从高到低遍历每个向量,若ans加上当前的向量会变大,就将其贪心的加上

最小值是线性基中最小的向量

查询第k小值

首先在去重的前提下。

首先我们求出对角线矩阵

那么,如果 \(k=(100101)_2\),我们所求的即为\(base_0 +base_2+base_5\)

也就是各位所对应的数Xor起来即可

查询和是第几大

如果在去重的前提下,和查询第k小差不多。

如果不在去重的前提下的话:

\(m\)为给出数的个数, \(\mathfrak{B}\)为所求线性基

结论:每个数都出现一样的次数,且这个次数为 \(2^{m - \vert \mathfrak{B}\vert}\)

证明:

所有不在线性基中的数的个数为 \(m - \vert \mathfrak{B}\vert\),我们任意选择它的一个子集 \(S\),对于 \(S\) 中的每个数 \(v\),有唯一的方式表达为 \(\mathfrak {B}\)中向量的线性组合。我们对于每个 \(v\),将这个线性组合中的向量都选上(一个向量选多次可以看作是次数对\(2\)取模后的结果),两个相同的数异或起来得到 \(0\),所以对于每个数 \(x\),我们都能找到至少\(2^{n - \vert \mathfrak{B}\vert}\) 种不同的选择方案,使得异或值为 \(x\)。又因为对于每个子集 \(S\),为了使得最终异或值为 \(x\),选择线性基中的向量的方案是唯一的,所以上界也是 \(2^{n - \vert \mathfrak{B}\vert}\)

(此结论同时出现在2019/2/24 福建四校联考中的T1)

总结

观察大佬博客Sengxian'blog后......

线性基的题型相对比较固定,看到下面的类型基本上都是线性基了:

  1. 最大异或和 (例题:luogu模板题)
  2. \(k\)大异或和/异或和是第几大 (例题:bzoj 2844)
  3. 求所有异或值的和 (例题:bzoj 3811)

线性基中的题目中还用到一个技巧:

  • 任意一条\(1\)\(n\) 的路径的异或和,都可以由任意一条 \(1\)\(n\) 路径的异或和与图中的一些环的异或和来组合得到。(例题:bzoj 2115)

这便是线性基的全部东西了。

Code

洛谷P3812

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x*f;
}
#define MN 55
ll n,base[MN],ans;
inline void insert(ll x)
{
    register int i,j;
    for(i=50;~i;--i)if(x>>i&1)
    {
        if(base[i]) x^=base[i];
        else
        {
            base[i]=x;
            for(j=i-1;~j;--j)if(base[j]&&(base[i]>>j&1))base[i]^=base[j]; 
            for(j=i+1;j<=50;++j)if(base[j]>>i&1)base[j]^=base[i];
            break;
        }
    }
}
int main()
{
    n=read();register int i;
    for(i=1;i<=n;++i) insert(read()); 
    for(i=0;i<=50;++i) ans^=base[i];
    return 0*printf("%lld\n",ans);
} 

bzoj 2115 Xor

link

dfs出所有的环

求出它们的线性基

#include<bits/stdc++.h>
#define ll long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x*f;
}
const int MN=200005,mN=50005,_=61; 
ll a[MN],tp,dfn[mN],dind,Xor[mN],b[_];
struct edge{int to;ll w;int nex;}e[MN<<1];
int N,M,hr[mN],en;
ll ans=0;
inline void ins(int x,int y,ll w)
{
    e[++en]=(edge){y,w,hr[x]};hr[x]=en;
    e[++en]=(edge){x,w,hr[y]};hr[y]=en;
}
inline void tj(int x,int f)
{
    register int i;
    dfn[x]=++dind;
    for(i=hr[x];i;i=e[i].nex)if(e[i].to^f)
    {
        if(!dfn[e[i].to]) Xor[e[i].to]=Xor[x]^e[i].w,tj(e[i].to,x);
        else a[++tp]=Xor[x]^Xor[e[i].to]^e[i].w;
    }
}
inline void calc()
{
    register int i,j,k;
    for(i=1;i<=tp;++i)for(j=_-1;~j;--j)if(a[i]>>j&1)
    {
        if(b[j]) a[i]^=b[j];
        else{b[j]=a[i];break;}
    }
    ans=Xor[N];
    for(i=_-1;~i;--i) if((ans^b[i])>ans) ans^=b[i];
}
int main()
{
    N=read();M=read();int x,y;
    while(M--) x=read(),y=read(),ins(x,y,read());
    tj(1,0);calc();
    return 0*printf("%lld\n",ans);
}

2019福建四校联考T1

link~

首先,容斥一下,转而求异或值为1的个数
\[ans=(2^n-1)(2^m-1)-num1\]
然后,对于一行,如果这一行的异或值不为0,它总有\(2^{m-1}\)个选法,使得选出的点的异或值是1(易证)
把每行当做一个数,我们只需要求出有哪些行的异或值为0
\[num1=2^{m-1}*(2^n-cnt0)\]
我们求出线性基,根据线性基的性质,异或值为0的个数一定是\(2^{n - \vert \mathfrak{B}\vert}\)

/*
2019/2/26 15:21
*/
#include<bits/stdc++.h>
#define ll long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x*f;
}
const ll mod=998244353,MN=2005;
std::bitset<MN> a[MN],base[MN];
int N,M,B;
ll ans;
inline void calc()
{
    register int i,j,k;
    for(i=1;i<=N;++i)for(j=M-1;~j;--j)
        if(a[i][j])
        {
            if(base[j].count()) a[i]^=base[j];
            else{base[j]=a[i];break;}
        }
    for(i=0;i<M;++i) if(base[i].count()) ++B;
}
inline ll fpow(ll x,int m)
{
    ll r=1;
    for(;m;m>>=1,x=1ll*x*x%mod) if(m&1) r=1ll*r*x%mod;
    return r;
}
int main()
{
    freopen("password.in","r",stdin);
    freopen("password.out","w",stdout);
    N=read();M=read();
    register int i,j;
    for(i=1;i<=N;++i)for(j=0;j<M;++j) a[i][j]=read()==1;
    calc();
    ans=(1ll*(fpow(2,N)-1)*(fpow(2,M)-1)%mod+mod)%mod;
    ans-=(1ll*fpow(2,M-1)*(fpow(2,N)-fpow(2,N-B)+mod)%mod)%mod;
    ans+=mod,ans%=mod;
    return 0*printf("%lld\n",ans);
}

暂时咕咕咕。


Blog来自PaperCloud,未经允许,请勿转载,TKS!

转载于:https://www.cnblogs.com/PaperCloud/p/10428084.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值