线性基入门
简介
线性基是一个集合
从原集合中选取任意多个数异或得到的值都能通过在线性基中选取一些数进行异或得到;
也就是说线性基是对原集合的压缩
构建方法
首先,可以知道
对于集合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中选取一些数进行异或得到
证:
从原集合A中选取一些数异或得到
x = ak1 ^ ak2 ^ ... ^ akm (kj∈[1,n])
如果这些数中不包含ai,那么这些数也在集合B中,x也能通过在B中取这些数异或得到,
如果包含ai,则ai可以用(ai ^ aj ) ^ aj 替换,故x也能由集合B得到
所以
对于任意一个集合,如果其中有两个元素的最高位相等,可以用异或将其中一个元素替换,如此下去,可以让该集合用某一位作为最高位的数唯一
代码实现
线性基中不能含有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;
}
例题
一、线性基求异或第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;
}
二、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;
}