线性基

简介

定义

对于一组数 a 1 , … , a n a_1,\ldots,a_n a1,,an,相应的线性基为 P 1 , … , P m P_1,\ldots,P_m P1,,Pm ,其中 P i P_i Pi 是最高位的1出现在第 i 位的数 。
线性基是一种特殊的基,它通常会在异或运算中出现,它的意义是:通过原集合 S S S的某一个最小子集 S 1 S1 S1,使得 S 1 S1 S1内元素相互异或得到的值域与原集合S相互异或得到的值域相同。

性质

1.线性基能相互异或得到原集合的所有相互异或得到的值。
2.线性基是满足性质1的最小的集合
3.线性基没有异或和为0的子集。

证明:
设线性基 S S S = { a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,,an};
若有子集 a 1 ⊕ a 2 ⊕ … ⊕ a t a_{1} \oplus a_{2} \oplus \ldots \oplus a_{t} a1a2at = 0 0 0,则 a 1 a_{1} a1= a 2 ⊕ a 3 ⊕ … ⊕ a t a_{2} \oplus a_{3} \oplus \ldots \oplus a_{t} a2a3at,则舍弃 a 1 a_{1} a1后一定能通过剩余的元素异或出 a 1 a_{1} a1。设 Y = a 1 ⊕ X Y=a_{1} \oplus X Y=a1X,因为 a 1 , a 2 , … , a n {a_1,a_2,\ldots,a_n} a1,a2,,an是一组线性基, X X X 一定能由 a 2 , … , a n a_2, \ldots,a_n a2,,an中相互异或得来。

Y Y Y= a 1 ⊕ X a_1 \oplus X a1X = a 2 ⊕ a 3 ⊕ … ⊕ a t ⊕ X a_2 \oplus a_3 \oplus \ldots \oplus a_t \oplus X a2a3atX a 2 , … , a t ⊕ Y a_2,\ldots,a_t \oplus Y a2,,atY也能得到 Y Y Y,所以 a 1 a_1 a1于线性基无用,与线性基是最小子集的定义矛盾。
所以,线性基没有异或和为0的子集

预备知识

向量空间(vector space):
定义 ( F , V , + , ⋅ (F, V, +, \cdot (F,V,+,)为向量空间(vector space),其中 F F F 为域, V V V 为集合, V V V 中元素称为向量,+ 为向量加法, ⋅ \cdot 为标量乘法,且运算满足 8 条公理:

F F F是一个域。一个 F F F上的向量空间是一个集合 V V V的两个运算:
向量加法: V + V → V V + V → V V+VV, 记作 v + w v + w v+w, ∃ v , w ∈ V ∃ v, w∈V v,wV
标量乘法: F × V → V F × V → V F×VV, 记作 a ⋅ v , ∃ a ∈ F , v ∈ V a·v, ∃a∈F, v∈V av,aF,vV
符合下列公理 ( ∀ a , b ∈ F 及 u , v , w ∈ V ) (∀ a, b ∈ F 及 u, v, w ∈ V) (a,bFu,v,wV)
1.向量加法结合律: u + ( v + w ) = ( u + v ) + w u + (v + w) = (u + v) + w u+(v+w)=(u+v)+w
2.向量加法交换律: v + w = w + v v + w = w + v v+w=w+v
3.向量加法的单位元: V V V 里有一个叫做零向量的 0 0 0 ∀ v ∈ V , v + 0 = v ∀ v \in V , v + 0 = v vV,v+0=v
4.向量加法的逆元素: ∀ v ∈ V , ∃ w ∈ V ∀v \in V, ∃w \in V vV,wV,使得 v + w = 0 v + w = 0 v+w=0
5.标量乘法分配于向量加法上: a ( v + w ) = a v + a w a(v + w) = a v + a w a(v+w)=av+aw
6.标量乘法分配于域加法上: ( a + b ) v = a v + b v (a + b)v = a v + b v (a+b)v=av+bv
7.标量乘法一致于标量的域乘法: a ( b v ) = ( a b ) v a(b v) = (ab)v a(bv)=(ab)v
8.标量乘法有单位元: 1 v = v 1 v = v 1v=v, 这里 1 是指域 F F F 的乘法单位元。

线性无关(linearly independent)
对于向量空间中 V V V n n n 个元素的向量组 ( v 1 , … , v n ) (\mathbf{v}_1, \ldots, \mathbf{v}_n) (v1,,vn),其线性组合(linear combination)是如下形式的向量

a 1 v 1 + a 2 v 2 + … + a n v n , 其 中 a 1 , … , a n ∈ F a_{1}\mathbf{v}_{1} + a_{2}\mathbf {v} _{2}+\ldots +a_{n}\mathbf {v} _{n},其中 a_1, \ldots, a_n \in F a1v1+a2v2++anvna1,,anF

若存在不全为 0 的数 a i ∈ F a_i \in F aiF,满足 a 1 v 1 + a 2 v 2 + … + a n v n = 0 a_{1}\mathbf {v} _{1}+a_{2}\mathbf {v} _{2}+\ldots +a_{n}\mathbf {v} _{n} = 0 a1v1+a2v2++anvn=0
则称这 n n n 个向量线性相关(linearly dependent),否则称为线性无关(linearly independent)。

一组向量线性无关 ⇔ \Leftrightarrow 没有向量可用有限个其他向量的线性组合所表示。


在线性代数中,基(也称为基底)是描述、刻画向量空间的基本工具。向量空间的基是它的一个特殊的子集,基的元素称为基向量。向量空间中任意一个元素,都可以唯一地表示成基向量的线性组合。如果基中元素个数有限,就称向量空间为有限维向量空间,将元素的个数称作向量空间的维数。
显然,基一定线性无关

构建方法

对于每一个数,从最高位到最低位扫,我们找出最高位的1出现在第 i 位的数, 如果此时 P i P_i Pi 为零,就将这个数加入线性基,否则异或 P i P_i Pi 继续找。然后我们就可以在 0 到 k 位上处理好每一位的线性基。这样得到的线性基保证每一位都能有对应的最大值。

从高到低考虑每个二进制位,设当前的答案为 a n s ans ans,考虑到第 k 位,线性基向量中代表二进制位 k 的向量为 v \mathbf{v} v。那么对于第 k 位,一共有三种情况,我们分别考虑我们的选择原则是不是正确的。

1. a n s ans ans 中第 k 位是 1, v \mathbf{v} v 中第 k 位是 1,实际上不能选。根据我们的选择原则,此时异或起来答案一定会变小,不选。正确。
2. a n s ans ans 中第 k 位是 0, v \mathbf{v} v 中第 k 位是1,实际上要选。根据我们的选择原则,此时异或起来答案一定会变大,选。正确。
3. v \mathbf{v} v 中第 k 位是 0,那么 v \mathbf{v} v 必定是零向量,选不选无所谓。正确。

应用

洛谷P3812(模板题)

题目描述
给定n个整数(数字可能重复),求在这些数中选取任意个,使得他们的异或和最大。

题解
求出相应的线性基。

P i P_{i} Pi101111011110111
P i − 1 P_{i-1} Pi1011110000000111
异或值110001011110000

第一种情况显然比第三种情况大;虽然第二种情况后三位全为1,但仍没有第一种情况(后三位全为0)大。因为线性基在构建时,相应的第i个数已满足该数二进制最高位1为第i位,1异或0仍为1,从而从高位到低位异或即为最大异或值。

也可以看看下面的证明(copy大佬的)

从高到低考虑每个二进制位,设当前的答案为 a n s ans ans,考虑到第 k 位,线性基向量中代表二进制位 k 的向量为 v \mathbf{v} v。那么对于第 k 位,一共有三种情况,我们分别考虑我们的选择原则是不是正确的。

1. a n s ans ans 中第 k 位是 1, v \mathbf{v} v 中第 k 位是 1,实际上不能选。根据我们的选择原则,此时异或起来答案一定会变小,不选。正确。
2. a n s ans ans 中第 k 位是 0, v \mathbf{v} v 中第 k 位是1,实际上要选。根据我们的选择原则,此时异或起来答案一定会变大,选。正确。
3. v \mathbf{v} v 中第 k 位是 0,那么 v \mathbf{v} v 必定是零向量,选不选无所谓。正确。

代码

#include <bits/stdc++.h>
#define LL long long

using namespace std;

const int maxn = 55;
LL a, p[maxn], n, ans;

inline LL read()
{
        LL n = 0;
        char ch;
        while (!isdigit(ch = getchar())) ;
        while (isdigit(ch))
                n =  n*10 + ch-'0', ch = getchar();

        return n;
}

int main()
{
        n = read();
        for (int i = 1; i <= n; i++)
        {
                a = read();
                for (int j = 50; j >= 0; j--)
                        if (a >> j & 1)
                        {
                                if (!p[j])
                                {
                                        p[j] = a;
                                        break;
                                }
                                a ^= p[j];
                        }
        }
        for (int j = 50; j >= 0; j--)
                ans = max(ans, ans^p[j]);
        printf("%lld", ans);

        return 0;
}

hdu3949(第K大异或值+高斯消元)

题目描述
输出一组数第K大的异或值(无重复),如果该组数所能表示的异或值小于K,输出-1

题解
我们求出向量空间 V V V 的一组线性基 p \mathfrak{p} p,不能简单的直接赋值,需要用到高斯消元,使得只有 p i \mathfrak{p}_i pi的第i位为1,其它的都不为1,。因为 p \mathfrak{p} p 中有且仅有一组线性组合能表示成向量空间中的任意向量 u \mathbf{u} u ,所以 p \mathfrak{p} p 中的任意非空子集都可以构成一个向量且互不重复。这些数能够组成的异或和的个数为 2 ∣ p ∣ 2^{\vert \mathfrak{p}\vert} 2p - 1。如果 ∣ p ∣ &lt; n \vert\mathfrak{p}\vert &lt; n p<n,则必然存在一个向量组满足线性相关性,这个向量组也就能通过线性组合,异或得到 0,则异或和的个数为 2 ∣ p ∣ 2^{\vert \mathfrak{p}\vert} 2p
假设线性基中有 m m m 个基向量,从大到小依次为 ( v m − 1 , … , v 0 ) (\mathbf{v}_{m-1}, \ldots, \mathbf{v}_{0}) (vm1,,v0),则第 k = ( b x … b 0 ) 2 (b_x\ldots b_0)_2 (bxb0)2小的数就是: ⊕ 0 ≤ i ≤ x b i ⋅ v i \oplus_{0\le i\le x}b_i \cdot v_i 0ixbivi,由线性基的性质易知结果的正确性。

代码

#include <bits/stdc++.h>
#define LL long long

using namespace std;

const int maxn = 10000+10;

int n, q, t;
LL a[maxn], b[66];

int main()
{
        scanf("%d", &t);
        for (int i = 1; i <= t; i++)
        {
                memset(b, 0, sizeof(b));
                printf("Case #%d:\n", i);
                scanf("%d", &n);
                for (int i = 1; i <= n; i++)
                        scanf("%lld", a+i);
                for (int i = 1; i <= n; i++)
                {
                        for (int j = 62; j >= 0; j--)
                                if (a[i] >> j &1)
                                {
                                        if (!b[j])
                                        {
                                                b[j] = a[i];
                                                //高斯消元,使得只有b[i]的第i为1,其它的都不为1
                                                for (int k = j-1; k >= 0; k--)
                                                        if (b[j] >> k & 1)
                                                                b[j] ^= b[k];
                                                for (int k = j+1; k <= 62; k++)
                                                        if (b[k] >> j & 1)
                                                                b[k] ^= b[j];
                                                break;
                                        }
                                       a[i] ^= b[j];
                                }

                }
                vector<LL> vec;
                for (int i = 0; i <= 62; i++)
                        if (b[i])
                                vec.push_back(b[i]);
                int cnt = vec.size();
                scanf("%d", &q);
                while (q--)
                {
                        LL k, ans = 0;
                        scanf("%lld", &k);
                        k -= cnt < n;//表示存在0
                        if (cnt < n && k == 0)
                        {
                                puts("0");
                                continue;
                        }
                        if (k >= (1LL << cnt))
                        {
                                puts("-1");
                                continue;
                        }
                        for (int i = 0; i < cnt; i++)
                                if (k >> i & 1)
                                        ans ^= vec[i];
                        printf("%lld\n", ans);
                }
        }

        return 0;
}

BZOJ 2115(图中最小异或路径)

题目描述
给定一个n节点m条边的无向图。求出图中最小异或路径(存在重边和自环)。

题解
任意一条包含环从1到n包含环的路径的异或和,都可以用任意一条不包含环从1到n的路径的异或和加上图中某些的环的异或和得到。先从起始点1考虑,假设我们直接走图中任意一个环,然后原路返回,由于从起始点到环的路径走过两遍,其异或和不影响相应环的异或和(异或两次结果为0)。可以利用DFS找出所有环,求出其相应的以异或和。
推广到图中的最大路径异或和,就可以用图中1到n路径的最大异或和,异或上图中能使结果更大的环。

代码

#include <bits/stdc++.h>
#define LL long long

const int maxn = 5e4+10;

int n, m, head[maxn*4], tot, t;
LL clique[maxn*4], d[maxn], p[66];
bool vis[maxn];
struct node
{
        int v, next;
        LL w;
};
node e[4*maxn];

void add(int u, int v, LL w)
{
        e[++t] = node{ v, head[u], w };
        head[u] = t;
}

void dfs(int u)
{
        vis[u] = 1;
        for (int i = head[u]; ~i; i = e[i].next)
        {
                int v = e[i].v;
                if (vis[v])
                        clique[++tot] = d[v] ^ e[i].w ^ d[u];
                else
                        d[v] = e[i].w ^ d[u], dfs(v);
        }
}

int main()
{
        scanf("%d%d", &n, &m);
        memset(head, -1, sizeof(head));
        for (int i = 1; i <= m; i++)
        {
                int s, t;
                LL d;
                scanf("%d%d%lld", &s, &t, &d);
                add(s, t, d);
                add(t, s, d);
        }
        dfs(1); // 找到所有的环
        // 根据环的异或和建一个线性基
        for (int i = 1; i <= tot; i++)
                for (int j = 62; j >= 0; j--)
                        if (clique[i] >> j & 1)
                        {
                                if (!p[j])
                                {
                                        p[j] = clique[i];
                                        break;
                                }
                                clique[i] ^= p[j];
                        }
        for (int i = 62; i >= 0; i--)
                if (d[n] < (d[n] ^ p[i]))
                        d[n] ^= p[i];

        printf("%lld", d[n]);

        return 0;
}

BZOJ2844(升序异或和集合中的最小下标)

题目描述
给定一个数Q,求Q在一组数升序异或集合中的最小下标。

题解
在集合中每个数都出现一样的次数,且由hdu3949的结论( p \mathfrak{p} p 中的任意非空子集都可以构成一个向量且互不重复。这些数能够组成的异或和的个数为 2 ∣ p ∣ 2^{\vert \mathfrak{p}\vert} 2p - 1。如果 ∣ p ∣ &lt; n \vert\mathfrak{p}\vert &lt; n p<n,则必然存在一个向量组满足线性相关性,这个向量组也就能通过线性组合,异或得到 0,则异或和的个数为 2 ∣ p ∣ 2^{\vert \mathfrak{p}\vert} 2p ),易知这个次数为 2 n − ∣ p ∣ 2^{n - \vert \mathfrak{p}\vert} 2np
所有的数不在线性基中的个数为 n - ∣ p ∣ \vert \mathfrak{p}\vert p,我们任选一个数 v, p \mathfrak {p} p 中向量有唯一的线性组合表示 v 。我们对于每个 v,将这个线性组合中的向量都选上(两个相同的数异或起来得到 0),所以对于每个数 v,我们都能找到 2 n − ∣ p ∣ 2^{n - \vert \mathfrak{p}\vert} 2np种不同的选择方案,使得异或值为 v。

代码

#include <bits/stdc++.h>
 
using namespace std;
 
const int mod = 10086;
 
int n, q, a[40], cnt, ans;
 
inline int read()
{
        int n = 0, ch;
        while (!isdigit(ch = getchar()));
        while (isdigit(ch))
                n = n*10 + ch-'0', ch = getchar();
 
        return n;
}
 
int pow_(int a, int b)
{
        int res = 1;
        while (b)
        {
                if (b&1)
                        (res *= a) %= mod;
                (a *= a) %= mod;
                b >>= 1;
        }
 
        return res;
}
 
int main()
{
        n = read();
        for (int i = 1; i <= n; i++)
        {
                int t = read();
                for (int j = 30; j >= 0; j--)
                        if (t >> j & 1)
                        {
                                if (!a[j])
                                {
                                        a[j] = t;
                                        cnt++;
                                        break;
                                }
                                t ^= a[j];
                        }
        }
        vector<int> vec;
        q = read();
 
        for (int i = 0; i <= 30; i++)
                if (a[i])
                        vec.push_back(i);
 
        for (int i = 0; i < vec.size(); i++)
                if (q >> vec[i] & 1)
                        ans += 1 << i;
 
        printf("%d\n", (ans % mod * pow_(2, n-cnt) % mod + 1) % mod);
 
        return 0;
}


CF1100F(线性基+离线)

题目描述
有n个汉堡店,每个店里最贵的汉堡是 c i c_i ci元, q q q次询问,从 l l l r r r,所能花费的最多钱数(结束与开始时的金额差),手里有d元,要花费c元,每次支付后,他还有d ⊕ \oplus c元。

题解
r r r为下标,将询问离线插入 Q [ r ] Q[r] Q[r] 数组中。而这时数组元素有个变量定义为 b b b ,表示这个元素目前所处的位置。显然当询问到位置 l l l时,满足 l ≤ b l\le b lb,才考虑该位置。
遍历 1 ≤ i ≤ n 1 \le i \le n 1in ,在第 i i i 个汉堡店时,在构造线性基 t \mathfrak{t } t 时,将 c i c_i ci, i i i 两个变量赋值到结构体变量 t e m p temp temp 的元素 a a a b b b 中,从高位到低位判断 t e m p . a temp.a temp.a 的值,需要注意的是,考虑 t j t_j tj 能异或尽量多的汉堡店,故在所有满足需求的线性基中要使 t [ j ] . b t[j].b t[j].b 尽可能的大,使 l ≤ t [ j ] . b l\le t[j].b lt[j].b成立,。当满足 t j t_j tj 存在且 t [ j ] . b &lt; t e m p . b t[j].b \lt temp.b t[j].b<temp.b,就交换位置。遍历 Q [ i ] Q[i] Q[i]数组,如果 Q [ i ] [ k ] . a ≤ t [ j ] . b Q[i][k].a \le t[j].b Q[i][k].at[j].b ,说明 t j t_j tj 满足第 t m p . b tmp.b tmp.b 组的询问,选出最大的 a n s ans ans 即可。

代码

#include <bits/stdc++.h>
#define LL long long

using namespace std;

inline int read()
{
        int n = 0;
        char ch;
        while (!isdigit(ch = getchar())) ;
        while (isdigit(ch))
                n = n*10 + ch-'0', ch = getchar();

        return n;
}

const int maxn = 5e5 + 10;
const int maxm = 25; // 实际20

int n, c[maxn], q, a, b, ans[maxn];
struct node
{
        int a, b;
} t[maxm];
vector<node> Q[maxn];

int main()
{
        n = read();
        for (int i = 1; i <= n; i++)
                c[i] = read();
        q = read();
        for (int i = 1; i <= q; i++)
        {
                a = read();
                b = read();
                Q[b].push_back(node { a, i });
        }
        for (int i = 1; i <= n; i++)
        {
                node temp = node { c[i], i };
                for (int j = 20; j >= 0; j--)
                        if (temp.a >> j & 1)
                        {
                                if (!t[j].a)
                                {
                                        t[j] = temp;
                                        break;
                                }
                                else if (t[j].b < temp.b)
                                        swap(t[j], temp);
                                temp.a ^= t[j].a;
                        }
                for (int k = 0; k < Q[i].size(); k++)
                {
                        node tmp = Q[i][k];
                        // tmp.b为当前询问第几组数据
                        for (int j = 20; j >= 0; j--)
                                if (t[j].b >= tmp.a)
                                        ans[tmp.b] = max(ans[tmp.b], ans[tmp.b] ^ t[j].a);
                }
        }
        for (int i = 1; i <= q; i++)
                printf("%d\n", ans[i]);

        return 0;
}

2019牛客多校第四场B(线段树维护线性基求交)

题目描述
给定n个序列,每个序列sz个数,求 [ l , … , r ] [l, \ldots, r] [l,,r] 的序列能否表示x 。

题解
建立线段树,区间查询线性基交集。
考虑子节点的线性基 B 1 , B 2 B_1, B_2 B1,B2 ,显然父节点为 B 1 ⋂ B 2 B_1 \bigcap B_2 B1B2 ,从低位到高位遍历 B 2 B_2 B2 的线性基,如果 B 2 B_2 B2 的第 i i i 位对应的数在线性基 B 1 B_1 B1 中,就将相应的异或结果赋值到父节点对应的线性基中。
代码

#include <bits/stdc++.h>
#define LL long long
 
using namespace std;
 
const int maxn = 5e4+10;
const int maxm = 35;
 
struct node
{
        int a[maxm];
};
 
node tree[maxn << 2];
int n, m, a, b, x;
int size[maxn], v[maxn][maxm];
 //成功插入线性基返回1,否则返回0
int insert(int p, int x, int flag)
{
        for (int i = 31; i >= 0; i--)
                if (x >> i & 1)
                {
                        if (!tree[p].a[i])
                        {
                                if (flag)
                                        tree[p].a[i] = x;
                                return 1;
                        }
                        x ^= tree[p].a[i];
                }
        return 0;
}
 
void merge(int p, node B1, node B2)
{
        node temp;
        for (int i = 0; i < 32; i++)
                temp.a[i] = B1.a[i], tree[p].a[i] = 0;
        for (int i = 0; i < 32; i++)
                if (B2.a[i])
                {
                        int t1 = B2.a[i]; // B2的第i位
                        int t2 = 0; // B1和B2异或交集
                        for (int j = i; j >= 0; j--)
                                if (t1 >> j & 1)
                                {
                                        if (!temp.a[j])
                                        {
                                                temp.a[j] = t1;
                                                B1.a[j] = t2;
                                                break;
                                        }
                                        t1 ^= temp.a[j];
                                        t2 ^= B1.a[j];
                                        // t1在B1中
                                        if (!t1)
                                        {
                                                tree[p].a[i] = t2;
                                                break;
                                        }
                                }
                }
}
 
void build(int p, int l, int r)
{
        if (l == r)
        {
                for (int i = 0; i < size[l]; i++)
                        insert(p, v[l][i], 1);
                return ;
        }
        int mid = (l+r) >> 1;
        build(p << 1, l, mid);
        build(p << 1 | 1, mid+1, r);
        merge(p, tree[p<<1], tree[p<<1|1]);
}
 
int query(int p, int l, int r)
{
        if (a <= l && b >= r)
                return !insert(p, x, 0);
        int mid = (l+r) >> 1;
        if (a <= mid && !query(p<<1, l, mid))
                return 0;
        if (b > mid && !query(p<<1|1, mid+1, r))
                return 0;
}
 
int main()
{
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++)
        {
                scanf("%d", size+i);
                for (int j = 0; j < size[i]; j++)
                        scanf("%d", &v[i][j]);
        }
        build(1, 1, n);
        while (m--)
        {
                scanf("%d%d%d", &a, &b, &x);
                printf("%s\n", query(1, 1, n) ? "YES" : "NO");
        }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值