线性基入门

简介

线性基是一个集合php

从原集合中选取任意多个数异或获得的值都能经过在线性基中选取一些数进行异或获得;node

也就是说线性基是对原集合的压缩c++

构建方法

首先,能够知道ui

对于集合A = {a1,a2,...,an},将其中的ai(i∈[1,n])用ai ^ aj (j∈[1,n]且j≠i)替换获得集合B = {a1,a2,...,ai-1,ai ^ aj,ai+1,...,an}
从集合A中选取任意多个数异或获得的值都能经过在集合B中选取一些数进行异或获得spa

证:code

从原集合A中选取一些数异或获得排序

x = ak1 ^ ak2 ^ ... ^ akm (kj∈[1,n])ip

若是这些数中不包含ai,那么这些数也在集合B中,x也能经过在B中取这些数异或获得,get

若是包含ai,则ai能够用(ai ^ aj ) ^ aj 替换,故x也能由集合B获得input

因此

对于任意一个集合,若是其中有两个元素的最高位相等,能够用异或将其中一个元素替换,如此下去,可让该集合用某一位做为最高位的数惟一

代码实现

线性基中不能含有0元素,若是插入一个元素会使线性基中存在零元素则单独讨论

struct node {
    ll b[65], p[65];
    int cnt, flag;
    node() {
        memset(p, 0, sizeof(p));
        memset(b, 0, sizeof(b));
        cnt = flag = 0;
    }
    //在线性基中插入一个元素
    bool insert(ll x) {
        for (int i = 62; i >= 0; i--) {
            if (x & (1LL << i)) {
                if (b[i]) {
                    x ^= b[i];
                }
                else {
                    b[i] = x;
                    return true;
                }
            }
        }
        flag = 1;
        return false;
    }
    //求异或最大值,即从高位向低位扫,若是能使答案更大就异或上该位的值
    ll qmax() {
        ll res = 0;
        for (int i = 62; i >= 0; i--) {
            if ((res ^ b[i]) > res) res ^= b[i];
        }
        return res;
    }
    //若是以前的插入出现了0,则返回0,不然返回线性基中最小的值
    ll qmin() {
        if (flag) return 0;
        for (int i = 0; i <= 62; i++) {
            if (b[i]) return b[i];
        }
        return 0;
    }
    //重构线性基,目的是为了求异或第k小的数
    void rebuild() {
        for (int i = 62; i >= 1; i--) {
            if (b[i]) {
                for (int j = i - 1; j >= 0; j--) {
                    if (b[i] & (1LL << j)) b[i] ^= b[j];
                }
            }
        }
        for (int i = 0; i <= 62; i++) {
            if (b[i]) p[cnt++] = b[i];
        }
    }
    //异或第k小,重构以后的线性基第i位对名次的贡献即为(1 << i),从低到高看看k的二进制位第i位是否为1,若为1则要异或上i为的值
    ll kth(ll k) {
        if (flag) --k;
        if (k == 0) return 0;
        ll res = 0;
        if (k >= (1LL << cnt)) return -1;
        for (int i = 0; i < cnt; i++) {
            if (k & (1LL << i)) res ^= p[i];
        }
        return res;
    }
};

线性基合并

暴力合并便可

node merge(node n1, node n2) {
    node res = n1;
    for (int i = 0; i <= 62; i++) {
        if (n2.b[i]) res.insert(n2.b[i]);
    }
    res.flag = n1.flag | n2.flag;
    return res;
}

例题

1、线性基求异或第K大

给定\(N\)(\(1<=N<=10000)\)个元素,\(Q\)(\(1<=Q<=10000)\)组询问,每次询问异或第k大是多少,不存在则输出-1

题目连接:HDU-3949 XOR

题解

线性基裸题

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct node {
    ll b[65], p[65];
    int cnt, flag;
    node() {
        memset(p, 0, sizeof(p));
        memset(b, 0, sizeof(b));
        cnt = flag = 0;
    }
    bool insert(ll x) {
        for (int i = 62; i >= 0; i--) {
            if (x & (1LL << i)) {
                if (b[i]) {
                    x ^= b[i];
                }
                else {
                    b[i] = x;
                    return true;
                }
            }
        }
        flag = 1;
        return false;
    }
    ll qmax() {
        ll res = 0;
        for (int i = 62; i >= 0; i--) {
            if ((res ^ b[i]) > res) res ^= b[i];
        }
        return res;
    }
    ll qmin() {
        if (flag) return 0;
        for (int i = 0; i <= 62; i++) {
            if (b[i]) return b[i];
        }
        return 0;
    }
    void rebuild() {
        for (int i = 62; i >= 1; i--) {
            if (b[i]) {
                for (int j = i - 1; j >= 0; j--) {
                    if (b[i] & (1LL << j)) b[i] ^= b[j];
                }
            }
        }
        for (int i = 0; i <= 62; i++) {
            if (b[i]) p[cnt++] = b[i];
        }
    }
    ll kth(ll k) {
        if (flag) --k;
        if (k == 0) return 0;
        ll res = 0;
        if (k >= (1LL << cnt)) return -1;
        for (int i = 0; i < cnt; i++) {
            if (k & (1LL << i)) res ^= p[i];
        }
        return res;
    }
};
node merge(node n1, node n2) {
    node res = n1;
    for (int i = 0; i <= 62; i++) {
        if (n2.b[i]) res.insert(n2.b[i]);
    }
    res.flag = n1.flag | n2.flag;
    return res;
}
int main() {
    int t;
    scanf("%d", &t);
    int cse = 0;
    while (t--) {
        int n;
        scanf("%d", &n);
        node lis;
        for (int i = 1; i <= n; i++) {
            ll x;
            scanf("%lld", &x);
            lis.insert(x);
        }
        cse++;
        printf("Case #%d:\n", cse);
        int m;
        scanf("%d", &m);
        lis.rebuild();
        for (int i = 1; i <= m; i++) {
            ll k;
            scanf("%lld", &k);
            printf("%lld\n", lis.kth(k));
        }
    }
    return 0;
}

2、BZOJ-2460 元素

Description

通过了大量的实验后,著名法师 Dmitri 发现:若是给如今发现的每一种矿石进行合理的编号(编号为正整数,称为该矿石的元素序号),那么,一个矿石组合会产生“魔法抵消”当且仅当存在一个非空子集,那些矿石的元素序号按位异或起来为零。 例如,使用两个一样的矿石必将发生“魔法抵消”,由于这两种矿石的元素序号相同,异或起来为零。
而且人们有了测定魔力的有效途径,已经知道了:合成出来的法杖的魔力等于每一种矿石的法力之和。人们已经测定了现今发现的全部矿石的法力值,而且经过实验推算出每一种矿石的元素序号。
如今,给定你以上的矿石信息,请你来计算一下当时能够炼制出的法杖最多有多大的魔力。

Input

第一行包含一个正整数N,表示矿石的种类数。

接下来 N行,每行两个正整数\(Number_i\)\(Magic_i\),表示这种矿石的元素序号和魔力值。

\(N<=1000,Number_i<=10^{18},Magic_i<=10^4\)

Output

仅包一行,一个整数:最大的魔力值

Sample Input

3 
1 10 
2 20 
3 30

Sample Output

50

题解

直接按val从大到小排序,优先插入val大的矿石,(最后线性基每一位都是固定的,因此优先插入val大的是最优的),成功插入统计答案便可

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct node {
    ll b[65], p[65];
    int cnt, flag;
    node() {
        memset(p, 0, sizeof(p));
        memset(b, 0, sizeof(b));
        cnt = flag = 0;
    }
    bool insert(ll x) {
        for (int i = 62; i >= 0; i--) {
            if (x & (1LL << i)) {
                if (b[i]) {
                    x ^= b[i];
                }
                else {
                    b[i] = x;
                    return true;
                }
            }
        }
        flag = 1;
        return false;
    }
    ll qmax() {
        ll res = 0;
        for (int i = 62; i >= 0; i--) {
            if ((res ^ b[i]) > res) res ^= b[i];
        }
        return res;
    }
    ll qmin() {
        if (flag) return 0;
        for (int i = 0; i <= 62; i++) {
            if (b[i]) return b[i];
        }
        return 0;
    }
    void rebuild() {
        for (int i = 62; i >= 1; i--) {
            if (b[i]) {
                for (int j = i - 1; j >= 0; j--) {
                    if (b[i] & (1LL << j)) b[i] ^= b[j];
                }
            }
        }
        for (int i = 0; i <= 62; i++) {
            if (b[i]) p[cnt++] = b[i];
        }
    }
    ll kth(ll k) {
        if (flag) --k;
        if (k == 0) return 0;
        ll res = 0;
        if (k >= (1LL << cnt)) return -1;
        for (int i = 0; i < cnt; i++) {
            if (k & (1LL << i)) res ^= p[i];
        }
        return res;
    }
};
node merge(node n1, node n2) {
    node res = n1;
    for (int i = 0; i <= 62; i++) {
        if (n2.b[i]) res.insert(n2.b[i]);
    }
    res.flag = n1.flag | n2.flag;
    return res;
}
const int N = 1050;
struct node1 {
    ll id, val;
    bool operator < (const node1 &b) const {
        return val > b.val;
    }
} a[N];
int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%lld%lld", &a[i].id, &a[i].val);
    }
    sort(a + 1, a + n + 1);
    node lis;
    ll ans = 0;
    for (int i = 1; i <= n; i++) {
        if (lis.insert(a[i].id)) ans += a[i].val;
    }
    printf("%lld\n", ans);
    return 0;
}
相关文章
相关标签/搜索