线性基总结

半腔热情 提交于 2019-12-20 02:32:05

线性基

  线性基是向量空间的一组基,通常可以解决有关异或的一些题目。通俗一点的讲法就是由一个集合构造出来的另一个集合,它有以下几个性质:

  • 线性基的元素能相互异或得到原集合的元素的所有相互异或得到的值。
  • 线性基是满足性质 1 的最小的集合。
  • 线性基没有异或和为 0 的子集。
  • 线性基中每个元素的异或方案唯一,也就是说,线性基中不同的异或组合异或出的数都是不一样的。
  • 线性基中每个元素的二进制最高位互不相同。
  • 线性基中每个元素的二进制最高位互不相同。

  构造线性基的方法如下

  对原集合的每个数\(x\)转为二进制,从高位向低位扫,对于第\(i\)位是 \(1\)的,如果\(p_i\)不存在,那么令\(p_i = x\)并结束扫描,如果存在,令 \(x = x\)\(\oplus\)\(p_i\)

inline void insert(long long x) {
  for (int i = 55; i + 1; i--) {
    if (!(x >> i))  // x的第i位是0
      continue;
    if (!p[i]) {
      p[i] = x;
      break;
    }
    x ^= p[i];
  }
}
  1. 查询原集合内任意几个元素\(xor\)的最大值,就可以用线性基解决。
  2. 将线性基从高位向低位扫,若 $xor \(上当前扫到的\)a_x$答案变大,就把答案异或上。为什么能行呢?因为从高往低位扫,若当前扫到第 \(i\)位,意味着可以保证答案的第\(i\)位为 \(1\),且后面没有机会改变第\(i\)位。

  3. 查询原集合内任意几个元素 \(xor\)的最小值,就是线性基集合所有元素中最小的那个。

  4. 查询某个数是否能被异或出来,类似于插入,如果最后插入的数\(x\)被异或成了\(0\),则能被异或出来。


相关题目

hdu3949 XOR
题意:给了N个数字,选择其中一些(甚至是一个数字)来对它们进行异或,可以得到许多不同的数字。求第k小的的数是多少?

我们先对所有的数求线性基,线性基异或得到的子集与原数异或得到的子集是相同的。而且线性基从高到低排列的,所以在求解第K大之类的问题时可以直接对k进行二进制分解,将1对应位置的基底异或得到答案。有个注意的问题就是0,因为线性基我们没有考虑0,所以0单独考虑,如果求出来线性基的个数等于插入数的个数,说明没解(相当于秩不等于未知数的个数),否则有0解

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 10005;
const int maxbit = 63;
#define ll long long
ll p[maxbit], a[maxn], L[maxbit];
int main()
{
    int T, cnt = 1;
    scanf("%d", &T);
    while (T--)
    {
        printf("Case #%d:\n", cnt ++);
        memset(p, 0, sizeof(p));
        memset(L, 0, sizeof(L));
        int n;
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i)
        {
            scanf("%lld", &a[i]);
        }
        for (int i = 1; i <= n; ++i)
        {
            for (int j = maxbit - 1; j >= 0; --j)
            {
                if ((a[i] >> j) & 1)
                {
                    if (!p[j])
                    {
                          p[j] = a[i];
                          break;
                    }
                    else
                    {
                        a[i] ^= p[j];
                    }
                }
            }
        }
        for (int i = maxbit - 1; i >= 0; --i)///构造单位矩阵
        {
            for (int j = i + 1; j <= maxbit - 1; ++j)
            {
                if ((p[j] >> i) & 1)
                {
                    p[j] ^= p[i];
                }
            }
        }

        int m = 0, Q;
        for (int i = 0; i < maxbit; ++i)
        {
            if (p[i])
                L[m++] = p[i];
        }
        scanf("%d", &Q);
        while (Q--)
        {
            ll k;
            scanf("%lld", &k);
            if (n != m)  /// m 是秩,如果秩不等於未知數的個數,有0解
                k--;
            if (k >= (1LL << m))
                puts("-1");
            else
            {
                ll ans = 0;
                for(int i = 0; i < maxbit; ++ i)
                {
                    if ((k >> i) & 1)
                        ans ^= L[i];
                }
                printf("%lld\n", ans);
            }
        }
    }
    return 0;
}

bzoj2115: [Wc2011] Xor
题意:给你一个无向连通图,结点编号从\(1-n\),求一条从1号节点到n号节点的路径,使得路径经过的边权值异或最大。每条路可以重复经过,权值在计算的时候也要相应多的次数

  这道题要求从1到n的最大xor和路径,存在重边,允许经过重复点、重复边。那么在图上作图尝试之后就会发现,路径一定是由许多的环和一条从1到n的路径组成。容易发现,来回走是没有任何意义的,因为来回走意味着抵消。考虑这道题求得是路径xor和最大,所以必然我们要想办法处理环的情况。我的做法是任意地先找出一条从1到n的路径,把这条路径上的xor和作为ans初值(先不管为什么可行),然后我们的任务就变成了求若干个环与这个ans初值所能组合成的xor最大值。显然,我们需要预处理出图上所有的环,并处理出所有环的环上xor值,这当然是dfs寻找,到n的路径的时候顺便求一下就可以了。
当我们得到了若干个环的xor值之后,因为是要求xor最大值,我们就可以构出这所有xor值的线性基。构出之后,再用ans在线性基上取max就可以了。

现在我们来讨论上述做法的可行性。

第一种情况:
  我们对最终答案产生贡献的某个环离1到n的主路径很远,这样的话,因为至少可以保证1可以到达这个环,那么我们可以走到这个环之后绕环一周之后原路返回,这样从1走到环的路上这一段被重复经过所以无效,但是环上的xor值被我们得到了,所以我们并不关心这个环和主路径的关系,我们只关心环的权值。
第二种情况:
  我们任意选取的到n的路径是否能保证最优性。假设存在一条更优的路径从1到n,那么这条路径与我们原来的路径构成了一个环,也就会被纳入线性基中,也会被计算贡献,假如这个环会被经过,那么最后的情况相当于是走了两遍原来选取的路径,抵消之后走了一次这个最优路径,所以我们无论选取的是哪条路径作为ans初值,都可以通过与更优情况构成环,然后得到一样的结果。这一证明可以拓展到路径上的任意点的路径选取。

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 5e5 + 7;
const int maxbit = 64;
int first[maxn];
ll xorsum[maxn], A[maxn << 1], p[maxbit];
int sign, sz;
bool vis[maxn];
struct node
{
    int to, next;
    ll w;
} edge[maxn*4];
void init()
{
    sign = 0;
    memset(first, -1, sizeof(first));
}
void add_edge(int u, int v, ll w)
{
    edge[sign].to = v;
    edge[sign].w = w;
    edge[sign].next = first[u];
    first[u] = sign++;
}
void dfs(int now, ll x)
{

    vis[now] = true;
    xorsum[now] = x;
    for (int i = first[now]; ~i; i = edge[i].next)
    {
        // puts("1");
        int to = edge[i].to;
        ll w = edge[i].w;
        if (vis[to])
        {
            A[++sz] = xorsum[now] ^ w ^ xorsum[to];
        }
        else
        {
            dfs(to, xorsum[now] ^ w);
        }
    }
}
int main()
{
    int n, m;
    scanf("%d %d", &n, &m);
    init();
    for (int i = 1; i <= m; ++i)
    {
        int u, v;
        ll w;
        scanf("%d%d%lld", &u, &v, &w);
        add_edge(u, v, w);
        add_edge(v, u, w);
    }
    dfs(1, 0);
    for (int i = 1; i <= sz; ++i)
    {
        for (int j = maxbit - 1; j >= 0; --j)
        {
            if ((A[i] >> j) & 1)
            {
                if (!p[j])
                {
                    p[j] = A[i];
                    break;
                }
                else
                {
                    A[i] ^= p[j];
                }
            }
        }
    }
    ll ans = xorsum[n];

    for(int j = maxbit - 1; j >= 0 ; --j)
    {
          ans = max(ans, ans ^ p[j]);
    }
    
    printf("%lld\n", ans);
    return 0;
}

牛客网 Wannafly挑战赛14 E.无效位置
题意:有N次操作,每次操作会使一个位置无效。一个区间的权值定义为这个区间里选出一些数的异或和的最大值。求在每次操作前,所有不包含无效位置的区间的权值的最大值。

  这题的话要求我们让一个位置无效,也相当于删除一个基,这并不好处理,因为线性基并没有删除操作,这我们考虑离线处理,从后往前求权值的最大值。把每次让一个位置无效装换成插入一个数。每次插入后,答案可能保留原先的最大值,或者插入的位置构成新的最大值。然后需要讨论的是插入的位置,是否与左右构成新的区间,如果当前插入位置为x,那么需要看x+1上是否存在值,如果有值,需要进行线性基的合并。以该区间最左边的位置作为合并后的线性基。可用并查集来维护这个位置。然后在看x-1位置上是否有值,如果有,用相同的方式进行线性基的合并。
  最后输出得到的线性基的最大值。

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e5 + 7;
const int maxbit = 32;
ll a[maxn], id[maxn], pre[maxn], vis[maxn];
struct L_B{
    ll p[maxbit],L[maxbit];
    ll cnt = 0;
    void add(int x){
        cnt++;
        for (int i = maxbit - 1; i >= 0; --i) {
            if ((x >> i) & 1){
                if (!p[i]){
                    p[i] = x;
                    break;
                }else{
                    x ^= p[i];
                }
            }
        }
    }
    ll quert_max(){
        ll ans = 0;
        for (int i = maxbit - 1; i >= 0; --i){
            ans = max(ans, ans ^ p[i]);
        }
        return ans;
    }
    L_B merge(L_B n1, L_B n2){
        L_B ret = n1;
        for (int i = maxbit - 1; i >= 0; i--)
            if (n2.p[i])
                ret.add(n2.p[i]);
        return ret;
    }
    ll quert_nth(ll k){
        for (int i = maxbit - 1; i >= 0; --i){
            for (int j = i + 1; j <= maxbit - 1; ++j){
                if ((p[j] >> i) & 1){
                    p[j] ^= p[i];
                }
            }
        }
        int r = 0;
        for (int i = 0; i < maxbit; ++i){
            if (p[i])
                L[r++] = p[i];
        }
        if (cnt != r)
            k--;
        if (k >= (1LL << r))
            return -1;
        else{
            ll ans = 0;
                for(int i = 0; i < maxbit; ++ i){
                    if ((k >> i) & 1)
                        ans ^= L[i];
                }
                return ans;
            }
    }

} M[maxn];

ll Find(ll x)
{
    ll r = x;
    while (pre[x] != x)
        x = pre[x];
    while (pre[r] != r)
    {
        int temp = pre[r];
        pre[r] = x;
        r = temp;
    }
    return x;
}
int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        pre[i] = i;
        scanf("%lld", &a[i]);
    }
    for (int i = 1; i <= n; i++)
    {
        scanf("%lld", &id[i]);
    }
    vector<ll> ve;
    ll ans = 0;
    for (int i = n; i >= 1; --i)
    {
        ll x = a[id[i]];
        vis[id[i]] = 1;
        M[id[i]].add(x);
        if (vis[id[i] + 1])
        {
            ll x = Find(id[i]);
            ll y = Find(id[i] + 1);
            pre[y] = x;
            M[x] = M[x].merge(M[x], M[y]);
        }
        if (vis[id[i] - 1])
        {
            ll x = Find(id[i] - 1);
            ll y = Find(id[i]);
            pre[y] = x;
            M[x] = M[x].merge(M[x], M[y]);
        }
        ll loc = Find(id[i]);
        ans = max(ans, M[loc].quert_max());
        ve.push_back(ans);
    }
    for (int i = ve.size() - 1; i >= 0; --i)
    {
        printf("%lld\n", ve[i]);
    }

    return 0;
}

【2019牛客暑期多校训练营(第一场) - H】XOR(线性基,期望的线性性)
题目大意:
给你n个数字,然后让你求所有满足 异或和为0的子集 的大小之和。

  根据期望的线性性,转化一下题意:相当于求每个数出现在子集中的次数之和。
  那么如何求每个的合法出现次数呢?对于每个数\(x\)的出现次数,也就是这个数\(x\)必选的情况下,有多少种选择方案可以让剩余\(n-1\)个数凑出\(x\)来。然后就可以\(x\oplus x=0\)
先对n个数求线性基b1,设线性基大小为r,可以分别计算线性基内数的贡献和线性基外数的贡献

  1. 线性基外:共\(n-r\)个数,枚举每个数,让他必选,将线性基外剩余的\(n-r-1\)个数任意排列(也可不选),则共有 \(2^{n-r-1}\)种方案,每种方案再异或\(x\)的结果,能被刚刚求出的那组线性基唯一的异或出来,所以每个数的贡献为:\(1\times2^{n-r-1} = 2^{n-r-1}\)。又因为一共有\(n-r\)个数,所以线性基外的数的贡献为:\((n-r)\times2^{n-r-1}\)
  2. 线性基内:依旧是枚举每个数\(x\)(注意枚举的\(x\)\(a\)数组中的数而不是\(b_1\)中的数),求出剩余n-1个数凑出x的方案数,做法是这样的:将所有剩余的\(n-1\)个数再求一次线性基,设为\(b_2\),分两种情况:

(1) \(x\)不能被\(b_2\)异或出。那么显然\(x\)不能在任意一个集合中出现,\(x\)的贡献为\(0\)

(2) \(x\)可以被\(b_2\)异或出。此时\(b_2\)的大小必定也为\(r\),因为\(b_2\)已经能表示所有\(n\)个数了。那么在除去\(x\)\(b_2\)的情况下,剩余\(n-r-1\)个数显然也是任意排列,\(x\)贡献为 \(2^{n-r-1}\)

  对于在线性基内的方案统计时,还有另一种理解:因为这个数\(x\)在线性基内,也就是说他代表了二进制中的一个\(Bit\)位在线性基中的存在,那么我们要凑出这个\(x\)的话(注意\(x\)依旧是原数,而不是线性基内的数字),就必须要找一个可以代表这个\(Bit\)位的数字来替代他,也就是:用除去\(x\)的剩余\(n-1\)个数,重构一组线性基后,如果基的大小仍为\(b_1.r\),则说明\(x\)在原基\(b_1\)中不是必须的,可以被替代,此时也才说明\(x\)对答案做出了贡献,不然的话只有\(x\)能代表这个\(Bit\),那他代表给谁看?和谁去异或成\(0\)?所以对答案没有任何贡献。所以写代码最后一部分的时候,既可以这样写:$if(b3.r == b1.r) $ 也可以这样写:\(if(b3.ins(a[i])==0)\)也就是说如果插不进去\(a[i]\)了,就代表他对答案做出了贡献,因为新基可以凑出所有数字。

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxbit = 64;
const int maxn = 1e5 + 7;
const int mod = 1e9 + 7;
#define ll long long
ll p[maxbit], b[maxbit], a[maxbit];
int insert(ll p[], ll x)
{
    for (int i = maxbit - 1; i >= 0; --i)
    {
        if ((x >> i) & 1)
        {
            if (!p[i])
            {
                p[i] = x;
                return true;
                break;
            }
            else
            {
                x ^= p[i];
            }
        }
    }
    return false;
}
ll quick_mul(ll a, ll b)
{
    ll ans = 1;
    while(b)
    {
        if(b&1)
        {
            ans = ans * a % mod;
        }
        b >>= 1;
        a = a * a % mod;
    }
    return ans % mod;
}
int main()
{
    ll n, r, ans, r2, temp;
    ios::sync_with_stdio(0);
    while (cin >> n)
    {
        memset(p, 0, sizeof(p));
        memset(a, 0, sizeof(a));
        r = 0, ans = 0, r2 = 0, temp = 0;
        vector<ll> ve;
        for (int i = 1; i <= n; ++i)
        {
            ll x;
            cin >> x;
            if (insert(p, x))
            {
                ++ r;
                ve.push_back(x);
            }
            else
            {
                if(insert(a, x))
                ++ r2;
            }
        }
        ans =  (n - r) % mod * quick_mul(2, n - r - 1)%mod%mod;
        for(int i = 0; i < r; ++i)
        {
            memcpy(b,a,sizeof(a));
            for(int j = 0;  j < r; ++j)
            {
                if( i != j )
                {
                    if(insert(b, ve[j]))
                    {
                        r2 ++;
                    }
                }
            }
            if(!insert(b,ve[i]))
            {
               ++ temp; 
            }
        }
        ans += (temp)*quick_mul(2,n-r-1)%mod;
        cout << ans % mod << endl;
    }
    return 0;
}

2019暑假杭电多校赛第一场B.Operation(前缀线性基求最大异或和+贪心)

杜教讲该题链接

题意:给你n个数,有两个操作,一个是在最后面插入x,一个是查询L到异或可得到的最大值。

官方题解:贪心地维护序列的前缀线性基 (上三角形态),对于每个线性基,将出现位置靠右的数字尽可能地放在高位,也就是说在插入新数字的时候,要同时记录对应位置上数字的出现位置,并且在找到可以插入的位置的时候,如果新数字比位置上原来的数字更靠右,就将该位置上原来的数字向低位推。在求最大值的时候,从高位向低位遍历,如果该位上的数字出现在询问中区间左端点的右侧且可以使答案变大,就异或到答案里。对于线性基的每一位,与它异或过的线性基更高位置上的数字肯定都出现在它右侧 (否则它就会被插入在那个位置了),因此做法的正确性显然。

(CodeForces 1100F)一篇关于线性基详细理解的博客

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 5e5 + 7;
const int maxbit = 32;
ll a[maxn], p[maxn][32], pos[maxn][32];
ll mod;
void add(ll id, ll x)
{
    ll k = id;
    for(int i = maxbit - 1; i >= 0; --i)
    {
        p[id][i] = p[id-1][i];
        pos[id][i] = pos[id-1][i];
    }
    for (int i = maxbit - 1; i >= 0; --i)
    {
        if ((x >> i) & 1)
        {
            if (!p[id][i])
            {
                p[id][i] = x;
                pos[id][i] = k;
                return ;
            }
            else
            {
                if (k > pos[id][i])
                {
                    swap(p[id][i], x);
                    swap(pos[id][i], k);
                }
                x ^= p[id][i];
            }
        }
    }
}
ll query_max(ll l, ll r)
{
    ll ans = 0;
    for (int i = maxbit - 1; i >= 0; --i)
    {
        if ((p[r][i] >> i) & 1)
        {
            if (((ans ^ p[r][i]) > ans) && pos[r][i] >= l)
                ans ^= p[r][i];
        }
    }
    return ans;
}

int main()
{
    ios::sync_with_stdio(false);
    int t;
    cin >> t;
    while (t--)
    {
        ll n, m, lastans = 0, mod;
        cin >> n >> m;
        for (int i = 1; i <= n; ++i)
        {
            cin >> a[i];
            add(i, a[i]);
        }
        for (int i = 1; i <= m; ++i)
        {
            int op;
            cin >> op;
            if (op == 0)
            {
                ll x, y;
                cin >> x >> y;
                x =((x ^ lastans) % n) + 1;
                y = ((y ^ lastans) % n) + 1;
                if (x > y)
                    swap(x, y);
                lastans = query_max(x, y);
                cout << lastans << endl;
            }
            else
            {
                n ++;
                ll x;
                cin >> x;
                x ^= lastans;
                add(n, x);
           
            }
        }
    }
    return 0;
}

2017 ICPC西安区域赛 A - XOR ,线段树合并线性基
题意:给个数组,每次询问一个区间你可以挑任意个数的数字异或和然后在or上k的最大值

题解:既然是要使得\(k | val\)得到的值最大,那么\(val\)必然是k这个数上二进制位为0的位置为1的数,同时1的位数要尽可能的多。这样我们就可以先对k取反,求出k二进制位为0的位数变成1的数p,再用\(A[i]\)与上\(p\),将这些数放入线性基中。由于每次都是区间查询,我们就可以利用线段树的思想,建立一棵结点为线性基的线段树,每次区间查询的时候就查询出这几个区间合并后的线性基,再用线性基的性质查询最大值即可

复杂度是$O(nlog(n)log(a_i)^2) $

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e4 + 7;
const int maxbit = 63;
int a[maxn];
struct L_B
{
    ll p[maxbit], L[maxbit];
    ll cnt = 0;
    void add(int x)
    {
        cnt++;
        for (int i = maxbit - 1; i >= 0; --i)
        {
            if ((x >> i) & 1)
            {
                if (!p[i])
                {
                    p[i] = x;
                    break;
                }
                else
                {
                    x ^= p[i];
                }
            }
        }
    }
    ll quert_max()
    {
        ll ans = 0;
        for (int i = maxbit - 1; i >= 0; --i)
        {
            ans = max(ans, ans ^ p[i]);
        }
        return ans;
    }
    L_B merge(L_B n2)
    {
        L_B ret ;
        for(int i = maxbit - 1; i >= 0; --i)
        ret.p[i] = p[i];
        for (int i = maxbit - 1; i >= 0; i--)
            if (n2.p[i])
                ret.add(n2.p[i]);
        return ret;
    }
}M[maxn*4];
struct segmentTree
{
    struct node
    {
        int l, r;
    } t[maxn * 4];
    void build(int v, int l, int r)
    {
        t[v].l = l;
        t[v].r = r;
        if (l == r)
        {
            memset(M[v].p, 0, sizeof(M[v].p));
            M[v].add(a[l]);
            return;
        }
        int mid = (l + r) / 2;
        build(v * 2, l, mid);
        build(v * 2 + 1, mid + 1, r);
       M[v] = M[v << 1].merge( M[v << 1 | 1]);
    }
    L_B query(int v, int l, int r)
    {
        if (t[v].l >= l && t[v].r <= r) ///正好匹配区间
        {
            return M[v];
        }
        int mid = (t[v].l + t[v].r) / 2;
        if (r <= mid)
        {
            return query(v * 2, l, r);
        }
        if (l > mid){
            return  query(v * 2 + 1, l, r);
        }
        else
        {
         return  query(v * 2, l, mid).merge(query(v * 2 + 1, mid + 1, r));
        }
    }
}T;

int main()
{
    int t;
   scanf("%d", &t);
    while (t--)
    {
        int n, q, k;
        scanf("%d%d%d", &n, &q, &k); 
        k = ~k;
        for (int i = 1; i <= n; ++i)
        {
            scanf("%d", &a[i]);
            a[i] = a[i]&k;
        }
        k = ~k;
            T.build(1,1,n);
        while(q--){
            L_B ans;
            memset(ans.p, 0, sizeof(ans.p));
            int l, r;
            scanf("%d%d",&l,&r);
            printf("%lld\n",k|T.query(1,l,r).quert_max());
        }
    }
    
    
    return 0;
}

2019牛客暑期多校训练营(第四场)B-xor(线段树+线性基求交)
题意:给你n个集合,每个集合中都有不超过32个数,总共询问m次,每次询问区间 [L, R] 中的所有集合,是否都有一个异或和等于X的子集。

线性基求交,用线段树来维护区间的交

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 100005;
const int maxbit = 33;
ll a[maxn];
struct L_B
{
    ll p[maxbit], L[maxbit];
    ll cnt = 0;
    L_B(){
        memset(p,0,sizeof(p));
    }
    void add(ll x)
    {
        cnt++;
        for (int i = maxbit - 1; i >= 0; --i)
        {
            if ((x >> i) & 1)
            {
                if (!p[i])
                {
                    p[i] = x;
                    break;
                }
                else
                {
                    x ^= p[i];
                }
            }
        }
    }
    bool quert_ans(ll x)
    {
        for (int i = maxbit - 1; i >= 0; --i)
        {
            if ((x >> i) & 1)
            {
                if (!p[i])
                {
                    return 0;
                }
                else
                {
                    x ^= p[i];
                }
            }
        }
        return 1;
    }
    L_B cross(L_B k){
        L_B res,tmp=k;
        for(int i=0;i<=maxbit -1;i++){
            ll x=p[i],y=0;
            bool flag=false;
            for(int j=maxbit - 1;j>=0;j--){
                if((x>>j)&1){
                    if(k.p[j]) x^=k.p[j],y^=tmp.p[j];
                    else{
                        k.p[j]=x;
                        tmp.p[j]=y;
                        flag=true;
                        break;
                    }
                }
            }
            if(!flag) res.p[i]=y;
        }
        return res;
    }
} M[maxn * 4];
struct segmentTree
{
    struct node
    {
        ll l, r;
    } t[maxn * 4];
    void build(ll v, ll l, ll r)
    {
        t[v].l = l;
        t[v].r = r;
        if (l == r)
        {
            ll sz;
            scanf("%lld",&sz);
            for(ll i = 1; i <= sz; ++i){
                ll x;
                scanf("%lld",&x);
                 M[v].add(x);
            }
            return;
        }
        ll mid = (l + r) / 2;
        build(v * 2, l, mid);
        build(v * 2 + 1, mid + 1, r);
        M[v] = M[v << 1].cross(M[v << 1 | 1]);
    }
    int query(ll v, ll l, ll r,ll x)
    {
        if (t[v].l >= l && t[v].r <= r) ///正好匹配区间
        {
            return M[v].quert_ans(x);
        }
        ll mid = (t[v].l + t[v].r) / 2;
        if (r <= mid)
        {
            return query(v * 2, l, r, x);
        }
        if (l > mid)
        {
            return query(v * 2 + 1, l, r, x);
        }
        else
        {
            return query(v * 2, l, mid, x)&(query(v * 2 + 1, mid + 1, r, x));
        }
    }
} T;

int main()
{
    ll n, q;
    scanf("%lld%lld", &n, &q);
        T.build(1, 1, n);
        for (int i = 1; i <= q; ++i)
        {
            ll l, r, x;
            scanf("%lld%lld%lld", &l, &r, &x);
            printf("%s\n", T.query(1, l, r, x) == 1 ? "YES" : "NO");
        }

    return 0;
}

附上自己的线性基模板

struct L_B{
    ll p[maxbit],L[maxbit];
    ll cnt;
    L_B(){
        cnt = 0;
        memset(p,0,sizeof(p));
        memset(L,0,sizeof(L));
    }
    void add(int x){
        cnt++;
        for (int i = maxbit - 1; i >= 0; --i) {
            if ((x >> i) & 1){
                if (!p[i]){
                    p[i] = x;
                    break;
                }else{
                    x ^= p[i];
                }
            }
        }
    }
    ll quert_max(){
        ll ans = 0;
        for (int i = maxbit - 1; i >= 0; --i){
            ans = max(ans, ans ^ p[i]);
        }
        return ans;
    }
    L_B merge(L_B n1, L_B n2){
        L_B ret = n1;
        for (int i = maxbit - 1; i >= 0; i--)
            if (n2.p[i])
                ret.add(n2.p[i]);
        return ret;
}
L_B cross(L_B k){///线性基求交
        L_B res,tmp=k;
        for(int i=0;i<=maxbit -1;i++){
            ll x=p[i],y=0;
            bool flag=false;
            for(int j=maxbit - 1;j>=0;j--){
                if((x>>j)&1){
                    if(k.p[j]) x^=k.p[j],y^=tmp.p[j];
                    else{
                        k.p[j]=x;
                        tmp.p[j]=y;
                        flag=true;
                        break;
                    }
                }
            }
            if(!flag) res.p[i]=y;
        }
        return res;
    }
    ll quert_nth(ll k){
        for (int i = maxbit - 1; i >= 0; --i){
            for (int j = i + 1; j <= maxbit - 1; ++j){
                if ((p[j] >> i) & 1){
                    p[j] ^= p[i];
                }
            }
        }
        int r = 0;
        for (int i = 0; i < maxbit; ++i){
            if (p[i])
                L[r++] = p[i];
        }
        if (cnt != r)
            k--;
        if (k >= (1LL << r))
            return -1;
        else{
            ll ans = 0;
                for(int i = 0; i < maxbit; ++ i){
                    if ((k >> i) & 1)
                        ans ^= L[i];
                }
                return ans;
            }
    }

} M[maxn];

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!