[学习笔记]线性基

线性基是什么

最常用的线性基是用来解决与异或有关的一类题目的,当然也有实数的线性基。
就是由一个原集合 S \mathbf{S} S 构造出另一个集合 T \mathbf{T} T,然后可以通过 T \mathbf{T} T 中任取若干个元素按照一定的规则进行组合得到 S \mathbf{S} S (比如说 T \mathbf{T} T 的张成为 S \mathbf{S} S)。感性上和向量的基底的特性比较像。

线性基的构造

异或线性基

每个数逐个插入线性基并维护。
二进制下拆分数 x x x,从高位向低位扫。

  1. 若第 k k k 位为 0 0 0,直接扫到下一位;
  2. 若第 k k k 位为 1 1 1,假如第 k k k 位的基 p k p_k pk 已经存在,那么就 x ← x xor ⁡ p k x \leftarrow x \operatorname{xor} p_k xxxorpk,然后扫下一位;反之, p k ← x p_k \leftarrow x pkx,插入完成,break

感性地解释一下原理:线性基中的每个基任意组合能且仅能异或出已插入线性基中的所有元素,每一位上都有一个代表基,代表的是最高位的 1 1 1。现在要插入一个新的元素,如果该元素的最高位的代表基已经存在,那么就要通过 x xor ⁡ p k x \operatorname{xor} p_k xxorpk 消去 x x x 最高位的 1 1 1 且保证 x ′ = x xor ⁡ p k x'=x \operatorname{xor} p_k x=xxorpk 异或线性基中的其他基不会产生新的元素。

Code:(以 Luogu P3812 为例)

void insert(ll x)
{
    for(int i = 49; i >= 0; --i)
    {
        if(!(x & (1LL << i)))
            continue;
        if(!p[i])
        {
            p[i] = x;
            break;
        }
        x ^= p[i];
    }
    return;
}

实数线性基

JLOI2015 装备购买 为例。
和常规的线性基内层逻辑上类似,也是把元素逐个插入并维护。
向量中的每一维有一个基,基进行任意组合可以构造出已插入的元素。每次插入一个新的元素,扫向量的每一维,若这一维上还没有代表基,那么就把该向量作为基;反之,通过高斯消元消去这一维,同时保证了新向量 z ′ ⃗ \vec{z'} z 与已有的基进行数乘和加减运算能得出原向量。

Code:

//代码片段,p是装备(结构体),a是装备的属性
for(int i = 1; i <= n; ++i)
{
    for(int j = 1; j <= m; ++j)
    {
        if(fabs(p[i].a[j]) <= eps) continue;
        if(b[j] == 0)
        {
            b[j] = i;
            break;
        }
        double mul = p[i].a[j] / p[b[j]].a[j];
        for(int k = j; k <= m; ++k)
            p[i].a[k] -= mul * p[b[j]].a[k];
    }
}

线性基性质

以最常规的线性基(用于异或的)为例。

  • 原数列里的任何一个数都可以通过线性基里的数异或表示出来;
  • 线性基里任意一个子集的异或和都不为 0 0 0
  • 一个数列可能有多个线性基,但是线性基里数的数量一定唯一,而且是满足第一条的基础上最少的。

其中第三条性质就为我们在一些题目中使用贪心算法提供了依据。

线性基应用

查询一个数是否可被异或出来

构造线性基,尝试插入待查询数 x x x
如果查询过程中 x x x 变为了 0 0 0,证明可被异或出来。

查询异或最大值

从高位到低位扫,贪心,如果异或这一位上的基能变大就异或。
原因:异或后最高位就不受影响了。

查询异或最小值

插入过程中特判能否异或和为 0 0 0,如果能,最小值就是 0 0 0;如果不能,最小值就是线性基中最小元素。

感性理解:线性基中的元素也是由原集合异或得到的,线性基中最小元素的最高位最低。

查询异或 k k k 小值

思路与在权值线段树查询 rank \text{rank} rank 类似。如果我们能让每一位 1 1 1 只在一个基上出现,那么我们就保证了在选完这个基后选其他基不会已经被选的 1 1 1 受到影响。

构造新数组 d d d,目标:通过消去其他基同一位上的 1 1 1 使得当前基上当前位的 1 1 1 仅存在于当前基。注意特判异或和能否为 0 0 0

例题:HDU-3949 XOR
代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 10010, maxk = 65;
ll a[maxn], p[maxk], d[maxk], zero;
int n, cnt;

void insert(ll x)
{
    for(int i = 64; i >= 0; --i)
    {
        if(!(x & (1LL << i)))
            continue;
        if(!p[i])
        {
            p[i] = x;
            return;
        }
        x ^= p[i];
    }
    zero = 1;
    return;
}

void rebuild()
{
    for(int i = 64; i >= 0; --i)
    {
        if(!p[i]) continue;
        for(int j = i - 1; j >= 0; --j)
            if(p[i] & (1LL << j))
                p[i] ^= p[j];
    }
    for(int i = 0; i <= 64; ++i)
        if(p[i])
            d[cnt++] = p[i];
    return;
}

ll query(ll k)
{
    if(k >= (1LL << cnt)) return -1;
    ll res = 0;
    for(int i = cnt - 1; i >= 0; --i)
        if(k & (1LL << i))
            res ^= d[i];
    return res;
}

void work()
{
    memset(p, 0, sizeof(p));
    memset(d, 0, sizeof(d));
    zero = 0;
    cnt = 0;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
    {
        scanf("%lld", a + i);
        insert(a[i]);
    }
    rebuild();
    int q;
    scanf("%d", &q);
    while(q--)
    {
        ll k;
        scanf("%lld", &k);
        printf("%lld\n", zero ? query(k - 1) : query(k));
    }
    return;
}

int main()
{
    int T;
    cin >> T;
    for(int i = 1; i <= T; ++i)
    {
        printf("Case #%d:\n", i);
        work();
    }
    return 0;
}
posted @ 2022-04-04 22:20  Andrewzdm  阅读(312)  评论(0)    收藏  举报