• 线性基


    xor问题的一种重要知识

    学习了这篇:Menci - 线性基学习笔记

    由于那一篇内容比较详尽,所以只提一点关键内容,方便记忆

    同时也补充了求线性基交/并的内容

    求线性基

    求线性基交/并

    例题

    暂时的板子(2020.5.4):

    typedef long long ll;
    const int M=60;
    
    //向线性基A中插入x 
    inline bool insert(ll *A,ll x)
    {
        for(int i=M-1;i>=0;i--)
            if((x>>i)&1)
                if(A[i])
                    x^=A[i];
                else
                {
                    A[i]=x;
                    return true;
                }
        return false;
    }
    
    //将矩阵化为对角线 方便从小到大枚举 
    void regulate(ll *A)
    {
        for(int i=0;i<M;i++)
        {
            if(!A[i])
                continue;
            for(int j=i+1;j<M;j++)
                if((A[j]>>i)&1)
                    A[j]^=A[i];
        }
    }
    
    
    //将线性基A,B求交 结果为W 
    inline void merge(ll *A,ll *B,ll *W)
    {
        static ll all[M],tmp[M];
        for(int i=0;i<M;i++)
            all[i]=A[i],tmp[i]=0;
        
        for(int i=M-1;i>=0;i--)
        {
            bool flag=false;
            ll x=B[i],prod=0;
            
            for(int j=M-1;j>=0;j--)
                if((x>>j)&1)
                    if(all[j])
                    {
                        x^=all[j];
                        prod^=tmp[j];
                    }
                    else
                    {
                        flag=true;
                        all[j]=x;
                        tmp[j]=B[i]^prod;
                        break;
                    }
            
            if(!flag && B[i])
                insert(W,B[i]^prod);
        }
    }
    View Code

    ~ 求线性基 ~

    跟矩阵中的线性基差不多,xor的线性基大概是这个意思:

    有一个长度为$n$的数组$a$,$m=max{lceil log_2 (a_i) ceil }$;令$A$为$a$的线性基,则任意$a_i,1leq ileq n$可以表示为线性基中几个数的xor

    求线性基的方法有点类似 用矩阵求方程组的消元过程

    若将$a$与$A$中的每个数分别用二进制拆成$m$位,则分别对应了$n imes m$与$m imes m$的矩阵

    我们的目标是将$a$数组对应的矩阵消元,并精简为一个$m imes m$的上三角矩阵(即$A$)

    具体是这样操作的:

    依次加入$a$中元素

    对于当前的$a_i$,令$val=a_i$,设最高的$1$在第$x$位(为了方便位运算,记$0leq x<m$)

       1. 若$A_x eq 0$,则$val=val xor A_x$,并继续判断最高的$1$

       2. 若$A_x=0$,则$A_x=val$

        对于所有$A_{j,x}=1,j eq i$,消去得$A_j=A_j xor val$(用$A_x$消其他)

        对于所有$A_{x,k}=1,k<x$,消去得$A_x=A_x xor A_k$(用其他消$A_x$)

    跟矩阵的求上三角是基本一致的

    (2的后面两步在很多情况下是不需要的:若没有这两步,矩阵仍然是上三角;只不过经过这两步处理后,具有了下面的性质2,于是能够从小到大地取能表示的xor值,具体见第一个例题)

    用这种方法求出的$A$,有这些性质:

       1. 对于$forall j>i,A_{i,j}=0$ (即二进制拆开后为上三角矩阵)

       2. 若$A_{i,i}=1$,则$forall j eq i,A_{i,j}=0$ (即最高位有独一性)

    代码实现不是很长

    #include <cstdio>
    #include <cstring>
    using namespace std;
    
    typedef long long ll;
    const int N=100005;
    const int M=60;
    
    void Linear_Basis(ll *a,int n,ll *A)
    {
        for(int i=1;i<=n;i++)
        {
            ll val=a[i];
            
            for(int j=M-1;j>=0;j--)
                if((val>>j)&1)
                {
                    if(A[j]!=0)
                    {
                        val^=A[j];
                        continue;
                    }
                    
                    A[j]=val;
                    for(int k=j-1;k>=0;k--)
                        if((A[j]>>k)&1)
                            A[j]^=A[k];
                    for(int k=j+1;k<M;k++)
                        if((A[k]>>j)&1)
                            A[k]^=A[j];
                    break;
                }
        }
    }
    
    int n;
    ll a[N];
    ll A[M];
    
    int main()
    {
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%lld",&a[i]);
        
        Linear_Basis(a,n,A);
        
        return 0;
    }
    View Code

    单步插入的线性基也是需要的,具体原因见第三个例题【其实这才是最常用的版本】

    inline bool Insert(ll *A,ll x)
    {
        for(int i=M-1;i>=0;i--)
            if((x>>i)&1)
            {
                if(A[i])
                {
                    x^=A[i];
                    continue;
                }
                
                A[i]=x;
                for(int j=i-1;j>=0;j--)
                    if((A[i]>>j)&1)
                        A[i]^=A[j];
                for(int j=i+1;j<M;j++)
                    if((A[j]>>i)&1)
                        A[j]^=A[i];
                return true;
            }
        return false;
    }
    View Code

    ~ 求线性基交/并 ~ 

    假设求交/并的对象是两组线性基$A,B$

    求线性基并,就是指求一组新的线性基$C$,使得任意能被$A,B$表示的数都能被$C$表示、且不能被$A,B$表示的数也不能被$C$表示

    而求线性基交,是指求一组新的线性基$C$,使得任意能被$C$表示的数都能被$A,B$表示、且不能被$C$表示的数也不能被$A,B$表示

    跟集合意义上的交/并有些差距

    求并是比较简单的:把$A,B$全部往$C$中插入就行了

    显然这样得出的线性基$C$能满足上面的要求

    但是求交就不那么显然了

    搜到的几篇讲的都跳了步骤,所以不如从头推一边

    设线性基$A$所对应的线性空间为$V_A$(就是$A$能表示的所有数的集合),线性基$B$所对应的线性空间为$V_B$

    令$W=V_Acap B$(即$W$是线性基$B$中能被$A$表示的基向量的集合)

    则有定理:若$Acup (Bsetminus W)$线性无关,那么$W$就是我们所想求的$A,B$的线性基交

    这个定理的证明是比较容易的:

    既然有$Acup (Bsetminus W)$线性无关,那么集合$Bsetminus W$所能表示的数显然都不能被$A$表示(因为$Acup (Bsetminus W)$也构成一组线性基,而线性基所张成的线性空间中,每一个数只有唯一的表示)

    而$W$中的基向量都能被$A$表示,$W$所能表示的数就更能被$A$表示了

    于是根据定义,$W$恰为线性基交

    但是我们不能保证对于线性空间$V_B$的任意一组线性基$B$,都有$Acup (Bsetminus W)$线性无关

    比如,想对$A={5},B={1,4}$求线性基交,显然结果是$C={5}$;但是$B$中的$1,4$都不能用$A$表示,且$1,4,5$线性相关

    于是我们所需要做的,就是调整出一组线性基$B'$(记$W'=V_Acap B'$),使得$Acup (B'setminus W')$线性无关

    还是上面的例子,我们可以调整出$B'={1,5}$,这样一来线性空间$V_B$不变,同时能顺利求出线性基交$C$

    这种调整是有规律可循的;简单点说,“调整”的过程就是在消除$B$对$B_i$的影响(等看完下面的过程就能理解这句话了)

    我们可以动态地维护一个线性基$all$,表示尝试插入当前$B$中基向量后,$Acup (Bsetminus W)$所对应的线性基

    我们依次将$B$中的基向量尝试插到$all$中(从高位到低位、从低位到高位皆可),假设当前插入基向量$B_i$

       1. $B_i$能够插进$all$中;这意味着$B_i$与当前的$Acup (Bsetminus W)$线性无关,那么显然$B_i otin W$,$B_iin all$

       2. $B_i$不能插进$all$中;这意味着$B_i$可以通过$Acup {B_0,B_1,...,B_{i-1}}$表示,那么$B_i otin all$,并需要进行调整、使得调整后的$B_i'in W$

          (1) 若$B_i$只用$A$即可表示,那么无需调整,$B_iin W$

          (2) 若$B_i$需要$A$及$B_{p_1},B_{p_2},...,B_{p_m}$($p_j<i,B_{p_j}in all$)才能表示,那么我们考虑$B_i'=B_i xor B_{p_1} xor ... xor B_{p_m}$

          显然,这时$B_i'$只用$A$就可以表示了

          同时,由于$B$是一组线性基,所以$B_i,B_i'$均与$B$中其他基向量线性无关;那么${B_1,...,B_{i-1},B_i',B_{i+1},...,B_n}$仍构成一组线性基

          这样的调整过后,就有$B_i'in W$

    具体是这样操作:

       0. 先做好准备工作,我们在合并过程中需要一个额外的线性基$all$,初始时$all=A$;需要一个数组$BB$,初始时全$0$,表示$B_i'$;需要一个变量$prod$,初始时为$0$,表示$B$整体对$B_i$的影响(有$B_i'=B_i xor prod$)

       1. 令$x=B_i$,尝试将$x$插入$all$,记当前的最高位$1$为第$j$位;若$all_j eq 0$,那么$x=x xor all_j$,$prod=prod xor BB_j$;若$all_j=0$,则插入成功,$all_j=x$,$BB_j=B_i xor prod$

       2. 若最终没有插入成功,那么将$B_i$调整后的值$B_i xor prod$插入结果线性基$W$中

    代码如下(由于可能需要将$B_i'$插入$W$,所以配有$Insert$函数)

    总复杂度$O(m^2)$,其中$m$为线性基的最大秩

    inline bool Insert(ui *A,ui x)
    {
        for(int i=M-1;i>=0;i--)
            if((x>>i)&1)
                if(A[i])
                    x^=A[i];
                else
                {
                    A[i]=x;
                    return true;
                }
        return false;
    }
    
    ui all[M],BB[M];
    
    inline void Merge(ui *A,ui *B,ui *W)
    {
        for(int i=0;i<M;i++)
            all[i]=A[i],BB[i]=0;
        
        for(int i=M-1;i>=0;i--)
        {
            bool flag=false;
            ui x=B[i],prod=0;
            
            for(int j=M-1;j>=0;j--)
                if((x>>j)&1)
                    if(all[j])
                    {
                        x^=all[j];
                        prod^=BB[j];
                    }
                    else
                    {
                        flag=true;
                        all[j]=x;
                        BB[j]=B[i]^prod;
                        break;
                    }
            
            if(!flag && B[i])
                Insert(W,B[i]^prod);
        }
    }
    View Code

    ~ 例题 ~

    最基本的例题:HDU 3949 ($XOR$)

    由于线性基能表示任意$a$的子集的xor值,所以只考虑从线性基$A$中选一个子集

    想求第$k$小的xor值,关键就是要搞清楚怎么按照顺序选

    先考虑最小值(这里需要特殊处理):若$A$的秩(即非$0$的$A_i$个数)小于$n$,则必存在$a$中的一个元素能被$a$中另外一些元素的xor值表示,那么最小值为$0$;否则最小值不为$0$

    然后选第$k$小就可以考虑成从$A$中按二进制选择(比如第$10$小,二进制为$1010$,就是$A$中第$4$小xor$A$中第$2$小)

    这样的正确性在于,若$i<j,A_i eq 0,A_j eq 0$,必有$A_0 xor ... xor A_i<2^{i+1}leq A_j$,所以选择$A_j$无论如何都比选择$A_0,...,A_i$中的子集更大

    #include <cstdio>
    #include <vector>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    typedef long long ll;
    const int N=100005;
    const int M=60;
    
    void Linear_Basis(ll *a,int n,ll *A)
    {
        for(int i=1;i<=n;i++)
        {
            ll val=a[i];
            
            for(int j=M-1;j>=0;j--)
                if((val>>j)&1)
                {
                    if(A[j]!=0)
                    {
                        val^=A[j];
                        continue;
                    }
                    
                    A[j]=val;
                    for(int k=j-1;k>=0;k--)
                        if((A[j]>>k)&1)
                            A[j]^=A[k];
                    for(int k=j+1;k<M;k++)
                        if((A[k]>>j)&1)
                            A[k]^=A[j];
                    break;
                }
        }
    }
    
    int n;
    ll a[N];
    ll A[M];
    
    int main()
    {
        int T;
        scanf("%d",&T);
        for(int it=1;it<=T;it++)
        {
            memset(A,0LL,sizeof(A));
            
            int n;
            scanf("%d",&n);
            for(int i=1;i<=n;i++)
                scanf("%lld",&a[i]);
            
            Linear_Basis(a,n,A);
            
            vector<ll> v;
            for(int i=0;i<60;i++)
                if(A[i])
                    v.push_back(A[i]);
            
            printf("Case #%d:
    ",it);
            int m=v.size(),q;
            scanf("%d",&q);
            
            while(q--)
            {
                ll x;
                scanf("%lld",&x);
                x=x-(m!=n);
                
                if((1LL<<m)-1<x)
                    printf("-1
    ");
                else
                {
                    ll ans=0;
                    for(int i=0;i<m;i++)
                        if((x>>i)&1)
                            ans^=v[i];
                    printf("%lld
    ",ans);
                }
            }
        }
        return 0;
    }
    View Code

    稍微进阶一点的题目:Luogu P3292 (辛运数字,$SCOI2016$)

    既然是树上路径上的问题,倍增是一个很容易想到的方向

    事实上,不仅可以通过倍增求出LCA,也可以通过倍增求出一点向根节点走$2^x$步时 路径上所有$G_i$所组成的线性基

    两个线性基的合并是$O(60^2)$的,即把一组插入到另一组中

    之后就是跟一般的倍增求LCA完全一样的处理了,只是多一个线性基而已

    时间复杂度$O(nlogncdot 60^2+mlogncdot 60)$,空间是$O(nlogncdot 60)$

    #include <cstdio>
    #include <vector>
    #include <cstring>
    using namespace std;
    
    typedef long long ll;
    const int N=20005;
    const int LOG=19;
    const int M=61;
    
    inline void Insert(ll *A,ll x)
    {
        for(int i=M-1;i>=0;i--)
            if((x>>i)&1)
                if(A[i])
                    x^=A[i];
                else
                {
                    A[i]=x;
                    return;
                }
    }
    
    int n,m;
    ll a[N];
    ll A[N][LOG][M];
    
    vector<int> v[N];
    int d[N],to[N][LOG];
    
    inline void dfs(int x,int fa)
    {
        d[x]=d[fa]+1;
        to[x][0]=fa;
        Insert(A[x][0],a[x]);
        
        for(int i=0;i<v[x].size();i++)
            if(v[x][i]!=fa)
                dfs(v[x][i],x);
    }
    
    ll base[M];
    
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%lld",&a[i]);
        for(int i=1;i<n;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            v[x].push_back(y);
            v[y].push_back(x);
        }
        
        dfs(1,0);
        for(int i=1;i<LOG;i++)
            for(int j=1;j<=n;j++)
            {
                int top=to[j][i-1];
                to[j][i]=to[top][i-1];
                
                for(int k=0;k<M;k++)
                    A[j][i][k]=A[j][i-1][k];
                for(int k=0;k<M;k++)
                    if(A[top][i-1][k])
                        Insert(A[j][i],A[top][i-1][k]);
            }
        
        while(m--)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            if(d[x]<d[y])
                swap(x,y);
            
            memset(base,0LL,sizeof(base));
            for(int i=LOG-1;i>=0;i--)
                if(to[x][i] && d[to[x][i]]>=d[y])
                {
                    for(int j=0;j<M;j++)
                        if(A[x][i][j])
                            Insert(base,A[x][i][j]);
                    x=to[x][i];
                }
            
            for(int i=LOG-1;i>=0;i--)
                if(to[x][i]!=to[y][i])
                {
                    for(int j=0;j<M;j++)
                    {
                        if(A[x][i][j])
                            Insert(base,A[x][i][j]);
                        if(A[y][i][j])
                            Insert(base,A[y][i][j]);
                    }
                    x=to[x][i],y=to[y][i];
                }
            if(x!=y)
            {
                Insert(base,a[x]);
                Insert(base,a[y]);
                Insert(base,a[to[x][0]]);
            }
            else
                Insert(base,a[x]);
            
            ll ans=0;
            for(int i=M-1;i>=0;i--)
                if((ans^base[i])>ans)
                    ans^=base[i];
            printf("%lld
    ",ans);
        }
        return 0;
    }
    View Code

    这题有点把我整自闭...:牛客ACM 881H ($XOR$,$2019$牛客暑期多校第一场)

    题解的思路十分优秀:求所有子集的大小之和,相当于对于每个元素,求包含该元素有多少集合

    先把所有$a_i$整出一组线性基$A$,大小为$m$

    对于所有不在线性基内的$n-m$个元素,若必选其中一个,则剩下的$n-m-1$个就可以任选,因为线性基总可以摆平所有情况;这样,对于这$n-m$个元素,共有$(n-m)cdot 2^{n-m-1}$的贡献

    而对于必选线性基内元素的情况,计算贡献就复杂一点了

       1. 若这个元素能被剩余$n-1$个元素表示,那么就相当于存在另一组不包含该元素的$m$元线性基,则剩下的$n-m-1$可以任选;于是有$2^{n-m-1}$的贡献

       2. 若这个元素不能被剩余$n-1$个元素表示,那么必选该元素时一定没有办法使整体xor值为$0$;于是贡献为$0$

    我们可以对基外的$n-m$个元素再求一次线性基$B$,于是线性基$B$能代表那$n-m$个基外元素

    那么想要判断是否存在不包含某基内元素$a_i$的另一组$m$元线性基,就相当于从$A$中移除$a_i$,并尝试加入$B$中的全部元素——这等效于对除了$a_i$外的全部元素做了一次线性基;得到一新线性基$C$

    这时只要判断将$a_i$加入$C$后,$C$的秩是否增大即可

    【我自己的坑点】

    坑点1:将$A$、$B$、$C$中的线性基最高位独一化

    其实这是毫无必要的,因为这题并不像上一题一样需要从小到大依次取出xor值;加了这一步骤会白白多一个$O(64)$的常数

    坑点2:若能通过替换一个元素得到一组新的线性基,则相替换的两个元素在线性基中的位置相同

    看起来很对,但实际上是个假结论

    在合成新的线性基时,应当先加入$B$中基,再加入$A$中基所对应的元素(不能直接加入$A$中的基,因为可能受到了被替换元素的影响)

    但在这时,由于先加进来的是$B$中的基,所以可能导致$A$中对应某元素所在位置与$A$中不相同

    比如自己找到的一个hack:

    $3$

    $7 4 4$

    在$A$中,根据加入的先后顺序,$A_2$对应元素为$7$,$A_1$对应元素为$4$,$B_2$对应元素为$4$;去除$7$,加入$4$,则$C_2=4,C_1=0$——$A_2$被$B$中$4$占了,而$A$中$4$因此失去了$A_1$的位置

    所以,碰到需要删除线性基的时候,不能总想玩骚的,还是老老实实写单步插入的比较好

    希望花在这题上的时间不要白费...

    #include <cstring>
    #include <cstdio>
    #include <vector>
    using namespace std;
    
    typedef long long ll;
    const int N=100005;
    const int M=60;
    const int MOD=1000000007;
    
    inline bool Insert(ll *A,ll x)
    {
        for(int i=M-1;i>=0;i--)
            if((x>>i)&1)
            {
                if(A[i])
                {
                    x^=A[i];
                    continue;
                }
                
                A[i]=x;
    /*            for(int j=i-1;j>=0;j--)
                    if((A[i]>>j)&1)
                        A[i]^=A[j];
                for(int j=i+1;j<M;j++)
                    if((A[j]>>i)&1)
                        A[j]^=A[i];*/
                return true;
            }
        return false;
    }
    
    int n;
    ll a[N];
    ll A[M],B[M],C[M];
    
    bool vis[N];
    vector<ll> v;
    
    inline ll quickpow(ll x,int t)
    {
        if(t<0)
            return 0;
        
        ll res=1;
        while(t)
        {
            if(t&1)
                res=res*x%MOD;
            x=x*x%MOD;
            t>>=1;
        }
        return res;
    }
    
    int main()
    {
        int n;
        while(~scanf("%d",&n))
        {
            v.clear();
            memset(A,0LL,sizeof(A));
            memset(B,0LL,sizeof(B));
            memset(vis,false,sizeof(vis));
            
            for(int i=1;i<=n;i++)
                scanf("%lld",&a[i]);
            
            for(int i=1;i<=n;i++)
                if(Insert(A,a[i]))
                {
                    vis[i]=true;
                    v.push_back(a[i]);
                }
            for(int i=1;i<=n;i++)
                if(!vis[i])
                    Insert(B,a[i]);
            
            int m=v.size(),dlt=0;
            for(int i=0;i<v.size();i++)
            {
                for(int j=0;j<M;j++)
                    C[j]=B[j];
                for(int j=0;j<v.size();j++)
                    if(j!=i)
                        Insert(C,v[j]);
                
                if(Insert(C,v[i]))
                    dlt++;
            }
            printf("%lld
    ",quickpow(2,n-m-1)*(n-dlt)%MOD);
        }
        return 0;
    }
    View Code

    杭电多校的一题:HDU 6579 ($Operation$,$2019 Multi-University Training Contest 1$)

    用线段树维护区间线性基是不太可行的,因为单次查询是$O((logn)^2)$

    题解给出的思路很秀:保存区间$[1,1],[1,2],...,[1,n]$的线性基,然后离线查询

    不过保存的区间$[1,i]$的线性基不是随随便便哪一组都行的

    当区间扩大、有一新元素加入时,线性基在两种情况下会被更新:

       1. 线性基的某一位为$0$,这时和一般情况一样直接加入

       2. 线性基的某一位不为$0$,但该基所对应元素在数组$a$中的下标小于当前元素的下标,那么就用当前元素(上三角消元后的值)作为基,并用被替换的基继续尝试更新线性基

    这样一来,想要查询$a_l,...,a_r$中的最大xor值,就相当于用区间$[1,r]$的线性基中 对应元素在$a$中下标大于等于$l$ 的基求最大xor值

    通过上述做法,能够使每个元素在不与下标更大的元素冲突的情况下 尽量作为位置最高的基,从而保证优先使下标靠后的元素作为基(需要脑补,有点绕orz...)

    学习了

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    using namespace std;
    
    const int N=1000005;
    const int M=30;
    
    int a[N];
    
    void Insert(int *A,int *pos,int x,int p)
    {
        for(int i=M-1;i>=0;i--)
            if((x>>i)&1)
            {
                if(A[i])
                {
                    if(p>pos[i])
                    {
                        swap(A[i],x);
                        swap(pos[i],p);
                    }
                    
                    x^=A[i];
                    continue;
                }
                
                A[i]=x;
                pos[i]=p;
                
    /*            for(int j=i-1;j>=0;j--)
                    if((A[i]>>j)&1)
                        A[i]^=A[j];
                for(int j=i+1;j<M;j++)
                    if((A[j]>>i)&1)
                        A[j]^=A[i];*/
                return;
            }
    }
    
    int n,m;
    int A[N][M];
    int pos[N][M];
    
    int main()
    {
        int T;
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d%d",&n,&m);
            for(int i=1;i<=n;i++)
                scanf("%d",&a[i]);
            
            for(int i=1;i<=n;i++)
            {
                for(int j=0;j<M;j++)
                {
                    A[i][j]=A[i-1][j];
                    pos[i][j]=pos[i-1][j];
                }
                Insert(A[i],pos[i],a[i],i);
            }
            
            int ans=0;
            while(m--)
            {
                int op,l,r,x;
                scanf("%d",&op);
                
                if(op==0)
                {
                    scanf("%d%d",&l,&r);
                    l=(l^ans)%n+1;
                    r=(r^ans)%n+1;
                    if(l>r)
                        swap(l,r);
                    
                    ans=0;
                    for(int i=M-1;i>=0;i--)
                        if((ans^A[r][i])>ans && pos[r][i]>=l)
                            ans^=A[r][i];
                    printf("%d
    ",ans);
                }
                else
                {
                    scanf("%d",&x);
                    x^=ans;
                    
                    a[++n]=x;
                    for(int i=0;i<M;i++)
                    {
                        A[n][i]=A[n-1][i];
                        pos[n][i]=pos[n-1][i];
                    }
                    Insert(A[n],pos[n],x,n);
                }
            }
        }
        return 0;
    }
    View Code

    今年多校看来跟线性基杠上了,补了不少知识点

    牛客ACM 884B ($xor$,$2019$牛客暑期多校第四场)

    首先按线段树那样,对区间求线性基交,是$O(32^2cdot n)$的

    难道在查询的时候也需要像线段树一样合并查询区间吗?那样要合并$logn$个小区间,单次复杂度为$O(32^2cdot logn)$

    其实不需要!我们只需要在$logn$个小区间内,尝试插入要查询的值$x$即可,若不能插入则该区间的set能表示$x$

    这样,单次复杂度降到了$O(32cdot logn)$

    这个操作十分巧妙,以后在特定题目中想对线段树的查询优化时,可以往这方面想

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    typedef unsigned int ui;
    const int N=50005;
    const int M=32;
    
    inline bool Insert(ui *A,ui x,int type)
    {
        for(int i=M-1;i>=0;i--)
            if((x>>i)&1)
                if(A[i])
                    x^=A[i];
                else
                {
                    if(type)
                        A[i]=x;
                    return true;
                }
        return false;
    }
    
    ui all[M],BB[M];
    
    inline void Merge(ui *A,ui *B,ui *W)
    {
        for(int i=0;i<M;i++)
            all[i]=A[i],BB[i]=0;
        
        for(int i=M-1;i>=0;i--)
        {
            bool flag=false;
            ui x=B[i],prod=0;
            
            for(int j=M-1;j>=0;j--)
                if((x>>j)&1)
                    if(all[j])
                    {
                        x^=all[j];
                        prod^=BB[j];
                    }
                    else
                    {
                        flag=true;
                        all[j]=x;
                        BB[j]=B[i]^prod;
                        break;
                    }
            
            if(!flag && B[i])
                Insert(W,B[i]^prod,1);
        }
    }
    
    int n,m;
    
    int sz=1;
    ui t[N<<2][M];
    
    inline bool Query(int k,int l,int r,int a,int b,int x)
    {
        if(a>r || b<l)
            return true;
        if(a>=l && b<=r)
            return !Insert(t[k],x,0);
        
        int mid=(a+b)>>1;
        return Query(k<<1,l,r,a,mid,x) && Query(k<<1|1,l,r,mid+1,b,x);
    }
    
    int main()
    {
        scanf("%d%d",&n,&m);
        while(sz<n)
            sz<<=1;
        
        for(int i=1;i<=n;i++)
        {
            int size;
            ui x;
            scanf("%d",&size);
            for(int j=1;j<=size;j++)
            {
                scanf("%u",&x);
                Insert(t[i+sz-1],x,1);
            }
        }
        for(int i=sz-1;i>=1;i--)
            Merge(t[i<<1],t[i<<1|1],t[i]);
        
        while(m--)
        {
            int l,r;
            ui x;
            scanf("%d%d%u",&l,&r,&x);
            printf(Query(1,l,r,1,sz,x)?"YES
    ":"NO
    ");
        }
        return 0;
    }
    View Code

    暂时感觉比较重要的内容都覆盖了?

    如果遇见其他比较新颖的相关问题就继续在后面补充

    Codeforces 1336E1  ($Chiori and Doll Picking (easy version)$)

    线性基好像只有你国出题人会出...

    首先看到$2^m$,可以往线性基考虑了;记线性基大小为$k$

    暴力做是8行的,因为需要枚举$2^k$种选法,在$m=k$时就爆炸了

    从题解抄的玩法,很有意思

    将线性基对角化,这样就可以通过最高位枚举线性基;为某线性基最高位的称为关键位,否则为非关键位

    记$dp[i][j][s]$表示按顺序选到第$i$个线性基、已选了$j$个关键位、非关键位状态为$s$,那么其可以向$dp[i+1][j][s]$和$dp[i+1][j+1][soplus x]$转移,其中$x$为第$i$个线性基去除关键位后的值(由于已经三角化,故只需要去除最高位)

    最后的结果就是对所有$dp[k][j][s]$进行统计,其贡献为$j+popcount(s)$

    然后学着scott_wu的方法优化了一下代码,不特意开$i$的一维,而仅用map维护$(j,s)$,并将新添的贡献先存起来、等到对map的遍历结束后再一起加上去;瞬间代码短了不少,跑的也快了10倍

    hard version绝赞咕咕咕~

    #include <map>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    typedef long long ll;
    typedef pair<int,ll> pii;
    const int M=60;
    const ll MOD=998244353;
    
    //向线性基A中插入x 
    inline bool Insert(ll *A,ll x)
    {
        for(int i=M-1;i>=0;i--)
            if((x>>i)&1)
                if(A[i])
                    x^=A[i];
                else
                {
                    A[i]=x;
                    return true;
                }
        return false;
    }
    
    int n,m;
    
    ll A[M];
    vector<pii> v;
    
    int popcount(ll x)
    {
        return __builtin_popcount(x>>20)+__builtin_popcount(x&((1<<20)-1));
    }
    
    ll ans[M];
    map<pii,ll> dp;
    
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            ll x;
            scanf("%lld",&x);
            Insert(A,x);
        }
        
        for(int i=0;i<M;i++)
            if(A[i])
            {
                v.push_back(pii(i,A[i]));
                for(int j=i+1;j<M;j++)
                    if((A[j]>>i)&1)
                        A[j]^=A[i];
            }
        
        int sz=v.size();
        dp[pii(0,0)]=1;
        
        for(auto i:v)
        {
            vector<pair<pii,ll> > add;
            ll x=i.second-(1LL<<i.first);
            
            for(auto it:dp)
            {
                int j=it.first.first;
                ll s=it.first.second,val=it.second;
                add.push_back(make_pair(pii(j+1,s^x),val));
            }
            
            for(auto j:add)
                dp[j.first]+=j.second;
        }
                
        for(auto it:dp)
            ans[it.first.first+popcount(it.first.second)]+=it.second;
        
        ll mul=1;
        for(int i=1;i<=n-sz;i++)
            mul=mul*2%MOD;
        
        for(int i=0;i<=m;i++)
            printf("%lld ",ans[i]*mul%MOD);
        return 0;
    }
    View Code

    OpenCup 18242A  ($Everyone Loves Playing Games$)

    Problem A. Everyone Loves Playing Games
    Input file: standard input
    Output file: standard output
    Time limit: 1 second
    Memory limit: 256 mebibytes
    
    One day, Acesrc and Roundgod are playing an interesting game called Important Choice Pairs of Cakes
    (ICPC).
    
    In this game, there is a variable X. Initially, X is equal to 0. N pairs of numbers (x_i, y_i) are given to
    Acesrc while M pairs (x'_i, y'_i) are given to Roundgod.
    
    Firstly, for every pair (x_i, yi_), Acesrc will choose either x_i or y_i. Suppose he choose k, X will be changed
    to (X ⊕ k). (⊕ denotes bitwise exclusive or)
    
    After Acesrc’s N operations, Roundgod will do the same with his M pairs.
    
    They know about each other’s pairs from the beginning. Acesrc wishes the final value of X to be as great
    as possible while Roundgod wishes it to be as small as possible.
    
    Acesrc and Roundgod are very clever boys and they will choose the best strategy. Can you predict the
    final value of X?
    
    Input
    
    There are multiple test cases. The first line of the input contains an integer T (1 ≤ T ≤ 20), indicating
    the number of test cases. For each test case:
    
    The first line contains two integers N and M (1 ≤ N, M ≤ 10000).
    
    Then N lines follow. In each line, there are two integers x_i, y_i (1 ≤ x_i, yi ≤ 10^18), representing Acesrc’s
    pairs.
    
    Then M lines follow. In each line, there are two integers x'_i, y'_i(1 ≤ x'_i, y'_i ≤ 10^18), representing Roundgod’s
    pairs.
    
    Output
    
    For each test case, you should output a single integer in a line as your answer.
    
    Example
    
    standard input
    2
    1 1
    6 3
    4 1
    2 2
    1 3
    4 6
    5 4
    2 2
    
    standard output
    2
    2
    
    Note
    
    In the first sample, if Acesrc chooses 6, Roundgod will choose 4 and the result will be 2.
    
    If Acesrc chooses 3, Roundgod will choose 1 and the result will also be 2.
    
    Therefore the answer is 2.
    题面

    考虑进行一个转化:默认全选$x_i$,那么是否想要改选$y_i$就变成了选$0$或是$x_ioplus y_i$

    于是考虑对$N$个$x_ioplus y_i$与$M$个$x'_ioplus y'_i$分别求线性基

    现在考虑如何让两人均optimal:若$N$个数的线性基中,有一些线性元插入到$M$个数的中时不会使秩增加,那么就相当于$M$个数总可以消除该线性元,于是可以不用考虑他们

    对于那些会使$M$的秩增加的$N$中的线性元,可以由$N$自由改变、而$M$不能改变

    于是将两个线性基均化成上三角,然后在$M$中选择线性元使得$ans$最小,再在$Nsetminus M$中选择线性元使得$ans$最大

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    typedef long long ll;
    const int M=60;
    
    //向线性基A中插入x 
    inline bool insert(ll *A,ll x)
    {
        for(int i=M-1;i>=0;i--)
            if((x>>i)&1)
                if(A[i])
                    x^=A[i];
                else
                {
                    A[i]=x;
                    return true;
                }
        return false;
    }
    
    //将矩阵化为对角线 方便从小到大枚举 
    void regulate(ll *A)
    {
        for(int i=0;i<M;i++)
        {
            if(!A[i])
                continue;
            for(int j=i+1;j<M;j++)
                if((A[j]>>i)&1)
                    A[j]^=A[i];
        }
    }
    
    int n,m;
    ll val;
    ll A[M],B[M],C[M];
    
    int main()
    {
        int T;
        scanf("%d",&T);
        while(T--)
        {
            val=0;
            memset(A,0LL,sizeof(A));
            memset(B,0LL,sizeof(B));
            
            scanf("%d%d",&n,&m);
            for(int i=1;i<=n;i++)
            {
                ll x,y;
                scanf("%lld%lld",&x,&y);
                val^=x;
                insert(A,x^y);
            }
            for(int i=1;i<=m;i++)
            {
                ll x,y;
                scanf("%lld%lld",&x,&y);
                val^=x;
                insert(B,x^y);
            }
            
            regulate(A);
            regulate(B);
            
            for(int i=0;i<M;i++)
                C[i]=B[i];
            for(int i=0;i<M;i++)
                insert(C,A[i]);
            
            regulate(C);
            
            for(int i=M-1;i>=0;i--)
                if(B[i] && ((val>>i)&1)==1)
                    val^=B[i];
            for(int i=M-1;i>=0;i--)
                if(C[i] && !B[i] && ((val>>i)&1)==0)
                    val^=C[i];
            
            printf("%lld
    ",val);
        }
        return 0;
    }
    View Code

    HDU 6863 (Kidnapper's Matching Problem,2020 Multi-University Training Contest 8)

    用$S$的线性基尽量消去$a,b$后,就是对$a$做$b$上的KMP了。

    (完)

  • 相关阅读:
    HttpWebRequest 的一个 异步封装
    AcWing 6 多重背包问题III【单调对列优化】
    AcWing 8. 二维费用的背包问题
    AcWing 1019. 庆功会
    P1421 小玉买文具
    P5709 【深基2.习6】Apples Prologue / 苹果和虫子
    P2181 对角线
    AcWing 1020. 潜水员
    AcWing 1013. 机器分配【分组背包+求方案数】
    AcWing 7. 混合背包问题
  • 原文地址:https://www.cnblogs.com/LiuRunky/p/Linear_Basis.html
Copyright © 2020-2023  润新知