【算法笔记】线性基

线性基

定义:给定数集\(s\),以异或运算张成的数集与\(S\)相同的极大线性无关集,称为原数集的一个线性基。

性质

  1. 原数集的任意一个数都能有线性基内部的一些数异或得到。
  2. 线性基内部任意数异或不为 0
  3. 线性基内数唯一,且保证性质一的情况下,数的个数最少。
  4. 线性基内每个数的最高有效位各不相同。

如何判断原数集能否异或出\(0\),在插入的过程中如果插入失败就可以异或出\(0\)

struct Basis {
    vector<ui64> B;
    bool zero, sorted;

    Basis() {
        B = vector<ui64>();
        zero = false, sorted = true;
    }

    void sort() {
        if (sorted) return;
        sorted = true;
        ranges::sort(B);
        return;
    }

    void insert(ui64 x) {
        for (auto b: B)
            x = min(x, b ^ x);
        if (x == 0) {
            zero = true;
            return;
        }
        for (auto &b: B)
            b = min(b, b ^ x);
        B.push_back(x), sorted = false;
        return;
    }

    int query(int kth = 1) { // query k-th smallest element
        sort();
        if (zero) kth--;
        ui64 ans = 0;
        for (auto b: B) {
            if (kth & 1) ans ^= b;
            kth >>= 1;
        }
        if (kth == 0) return ans;
        return -1;
    }

    ui64 max() {
        ui64 ans = 0;
        for (auto b: B)
            ans ^= b;
        return ans;
    }

    bool check(ui64 x) {
        for (auto b: B)
            x = min(x, b ^ x);
        return x == 0;
    }

    void merge(const Basis &oth) {
        for (auto x: oth.B)
            insert(x);
        return;
    }
};

例题

P3812 【模板】线性基

验证模板,考虑到线性基内数的最高有效位各不相同,所以直接把所有的数异或就是答案

#include<bits/stdc++.h>

using namespace std;

using i32 = int32_t;
using i64 = long long;

using ui64 = uint64_t;

struct Basis {
    vector<ui64> B;
    bool zero, sorted;

    Basis() {
        B = vector<ui64>();
        zero = false, sorted = true;
    }

    void sort() {
        if (sorted) return;
        sorted = true;
        ranges::sort(B);
        return;
    }

    void insert(ui64 x) {
        for (auto b: B)
            x = min(x, b ^ x);
        if (x == 0) {
            zero = true;
            return;
        }
        for (auto &b: B)
            b = min(b, b ^ x);
        B.push_back(x), sorted = false;
        return;
    }

    int query(int kth = 1) { // query k-th smallest element
        sort();
        if (zero) kth--;
        ui64 ans = 0;
        for (auto b: B) {
            if (kth & 1) ans ^= b;
            kth >>= 1;
        }
        if (kth == 0) return ans;
        return -1;
    }

    ui64 max() {
        ui64 ans = 0;
        for (auto b: B)
            ans ^= b;
        return ans;
    }

    bool check(ui64 x) {
        for (auto b: B)
            x = min(x, b ^ x);
        return x == 0;
    }

    void merge(const Basis &oth) {
        for (auto x: oth.B)
            insert(x);
        return;
    }
};


i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    Basis b;
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        ui64 x;
        cin >> x;
        b.insert(x);
    }
    cout << b.max();
    return 0;
}

P3857 [TJOI2008] 彩灯

线性基的元素有\(x\)个,每一个都有选或不选两种,方案数\(2^x\)

#include<bits/stdc++.h>

using namespace std;

using i32 = int32_t;
using i64 = long long;

using ui64 = uint64_t;

struct Basis {
    vector<ui64> B;
    bool zero, sorted;

    Basis() {
        B = vector<ui64>();
        zero = false, sorted = true;
    }

    void sort() {
        if (sorted) return;
        sorted = true;
        ranges::sort(B);
        return;
    }

    void insert(ui64 x) {
        for (auto b: B)
            x = min(x, b ^ x);
        if (x == 0) {
            zero = true;
            return;
        }
        for (auto &b: B)
            b = min(b, b ^ x);
        B.push_back(x), sorted = false;
        return;
    }

    int query(int kth = 1) { // query k-th smallest element
        sort();
        if (zero) kth--;
        ui64 ans = 0;
        for (auto b: B) {
            if (kth & 1) ans ^= b;
            kth >>= 1;
        }
        if (kth == 0) return ans;
        return -1;
    }

    ui64 max() {
        ui64 ans = 0;
        for (auto b: B)
            ans ^= b;
        return ans;
    }

    bool check(ui64 x) {
        for (auto b: B)
            x = min(x, b ^ x);
        return x == 0;
    }

    void merge(const Basis &oth) {
        for (auto x: oth.B)
            insert(x);
        return;
    }
};


i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    Basis b;
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        string s;
        cin >> s;
        i64 val = 0;
        for (auto c: s) {
            val <<= 1;
            val |= (c == 'O');
        }
        b.insert(val);
    }
    int t = b.B.size(), res = 1;
    for (int i = 0; i < t; i++)
        res = res * 2 % 2008;
    cout << res;
    return 0;
}

P4570 [BJWC2011] 元素

首先选出的集合内不能存在异或和为\(0\)的情况,这个可以用线性基来维护。

值最大可以贪心,优先插入魔力值大的。假设集合内存在\(a\oplus b \oplus c \oplus d = 0\)的情况,我们肯定扔掉最小值最优。

#include<bits/stdc++.h>

using namespace std;

using i32 = int32_t;
using i64 = long long;

using ui64 = uint64_t;

struct Basis {
    vector<ui64> B;
    bool zero, sorted;

    Basis() {
        B = vector<ui64>();
        zero = false, sorted = true;
    }

    void sort() {
        if (sorted) return;
        sorted = true;
        ranges::sort(B);
        return;
    }

    void insert(ui64 x) {
        for (auto b: B)
            x = min(x, b ^ x);
        if (x == 0) {
            zero = true;
            return;
        }
        for (auto &b: B)
            b = min(b, b ^ x);
        B.push_back(x), sorted = false;
        return;
    }

    int query(int kth = 1) { // query k-th smallest element
        sort();
        if (zero) kth--;
        ui64 ans = 0;
        for (auto b: B) {
            if (kth & 1) ans ^= b;
            kth >>= 1;
        }
        if (kth == 0) return ans;
        return -1;
    }

    ui64 max() {
        ui64 ans = 0;
        for (auto b: B)
            ans ^= b;
        return ans;
    }

    bool check(ui64 x) {
        for (auto b: B)
            x = min(x, b ^ x);
        return x == 0;
    }

    void merge(const Basis &oth) {
        for (auto x: oth.B)
            insert(x);
        return;
    }
};

using vi = vector<i64>;

i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    int n;
    cin >> n;
    vi number(n), magic(n), p(n);
    for (int i = 0; i < n; i++)
        cin >> number[i] >> magic[i], p[i] = i;
    ranges::sort(p, [&](const int x, const int y) {
        return magic[x] > magic[y];
    });
    Basis b;
    i64 res = 0;
    for (auto i: p) {
        if (b.check(number[i])) continue;
        b.insert(number[i]), res += magic[i];
    }
    cout << res;
    return 0;
}

P4301 [CQOI2013] 新Nim游戏

首先根据Nim游戏的性质,我们知道如果初始的异或和不为\(0\),先手必胜。

假设在第一轮先手操作后异或和不为 0,后手是不可能通过拿操作是的异或为 0 的。对于先手来说,极端情况就是保留一个,此时异或和一定不为0。所以先手必胜。

先手选取的方式用线性基来维护,总数最小参考上一题的贪心处理就好了。

#include<bits/stdc++.h>

using namespace std;

using i32 = int32_t;
using i64 = long long;

using ui64 = uint64_t;

struct Basis {
    vector<ui64> B;
    bool zero, sorted;

    Basis() {
        B = vector<ui64>();
        zero = false, sorted = true;
    }

    void sort() {
        if (sorted) return;
        sorted = true;
        ranges::sort(B);
        return;
    }

    void insert(ui64 x) {
        for (auto b: B)
            x = min(x, b ^ x);
        if (x == 0) {
            zero = true;
            return;
        }
        for (auto &b: B)
            b = min(b, b ^ x);
        B.push_back(x), sorted = false;
        return;
    }

    int query(int kth = 1) { // query k-th smallest element
        sort();
        if (zero) kth--;
        ui64 ans = 0;
        for (auto b: B) {
            if (kth & 1) ans ^= b;
            kth >>= 1;
        }
        if (kth == 0) return ans;
        return -1;
    }

    ui64 max() {
        ui64 ans = 0;
        for (auto b: B)
            ans ^= b;
        return ans;
    }

    bool check(ui64 x) {
        for (auto b: B)
            x = min(x, b ^ x);
        return x == 0;
    }

    void merge(const Basis &oth) {
        for (auto x: oth.B)
            insert(x);
        return;
    }
};

using vi = vector<i64>;

i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    int n;
    cin >> n;
    vi a(n);
    for (auto &i: a) cin >> i;
    ranges::sort(a, greater<>());
    Basis b;
    i64 res = 0;
    for (auto &i: a) {
        if (b.check(i)) res += i;
        else b.insert(i);
    }
    cout << res;
    return 0;
}
posted @ 2024-09-14 20:21  PHarr  阅读(17)  评论(0编辑  收藏  举报