线性基学习笔记

xor问题的一种重要知识

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

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

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

求线性基

求线性基交/并

例题

 


 

~ 求线性基 ~

 

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

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

 

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

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

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

 

具体是这样操作的:

依次加入$a$中元素

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

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

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

    对于所有$A_{j,x}=1,j\neq 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\neq 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_A\cap B$(即$W$是线性基$B$中能被$A$表示的基向量的集合)

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

 

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

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

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

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

 

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

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

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

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

 

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

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

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

   1. $B_i$能够插进$all$中;这意味着$B_i$与当前的$A\cup (B\setminus W)$线性无关,那么显然$B_i\notin W$,$B_i\in all$

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

      (1) 若$B_i$只用$A$即可表示,那么无需调整,$B_i\in 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\neq 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\neq 0,A_j\neq 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:\n",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\n");
            else
            {
                ll ans=0;
                for(int i=0;i<m;i++)
                    if((x>>i)&1)
                        ans^=v[i];
                printf("%lld\n",ans);
            }
        }
    }
    return 0;
}
View Code

 

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

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

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

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

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

时间复杂度$O(nlogn\cdot 60^2+mlogn\cdot 60)$,空间是$O(nlogn\cdot 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\n",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\n",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\n",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^2\cdot n)$的

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

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

这样,单次复杂度降到了$O(32\cdot 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\n":"NO\n");
    }
    return 0;
}
View Code

 


 

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

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

 

(完)

转载于:https://www.cnblogs.com/LiuRunky/p/Linear_Basis.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值