一.线性基的学习
定义
- 对于每个序列 {a1,a2,a3……an},其都有一个线性基 {b1,b2,b3……bm}。使得
- 1.线性基中的元素相互异或得到的集合与原序列相互异或得到的集合等价。
- 2.线性基是满足性质1的最小的集合,在log级别
- 3.线性基没有异或和为0的子集
- 4.线性基中通过不同操作异或成的值不相同(可以通过3证明)
算法学习
- 性质: 线性基中的元素二进制下最高位1在第 i 位的最多只有一个。那么很显然线性基元素数量在log级别
- 性质证明: 考虑将元素逐个加入的思路,设此时需要加入的元素为 x ,并设其二进制下最高位1在第 i 位,若线性基中已经有二进制下最高位1在第 i 位的元素 d[i] ,则其可以通过异或 d[i] 来得到一个新数字 y,去考虑 y 的插入,而 y 二进制下最高位一定比第 i 位低。由于x xor d[i] = y <=> x = d[i] xor y,x 仍可以在新序列中表示出来。当数字<=1e9,线性基数量大概32左右,当数字<=1e18,线性基大小大概在62左右。
- 限制条件: 线性基中的不可有0的出现,若出现0,则要特别标记
代码如下:(用flag表示是否有0的出现)
void insert(long long x){
for(int i=62;i>=0;i--){
if((1ll<<i)&x){
if(b[i])x^=b[i];
else {
b[i]=x;
return;
}
}
}
flag=1;
return;
}
int main(){
long long n,x;
cin>>n;
for(int i=1;i<=n;i++){
cin>>x;
insert(x);
}
}
二.线性基的基本操作
序列异或和的最大值
- 构造序列的线性基,从大到小,贪心的考虑是否要异或 d[i] 。
long long get_max(){
long long ans=0;
for(int i=62;i>=0;i--){
if((ans^b[i])>ans)ans=ans^b[i];
}
return ans;
}
异或最小值
- 构造序列的线性基,最小的 d[i] 就是序列异或和的最小值 。
long long get_min(){
if(flag)return 0;
if(int i=0;i<=62;i++){
if(b[i])return b[i];
}
return 0;
}
序列异或和的集合大小
- 由于性质4,保证线性基的元素相互异或得到的值不相同。即线性基的元素相当于一个开关,有和没有两种可能,假设线性基的元素有num个,那么集合大小为 (1<<num) - 1,但是由于线性基无法判断是否存在异或和为 0 的情况,所以还需特判一下。
long long size(){
long long num=0;
for(int i=62;i>=0;i--){
if(b[i]!=0)num++;
}
if(flag)return (1ll<<num);
else return (1ll<<num)-1;
}
异或第 k 小
- 方法:首先我们要对线性基进行重构。很显然,如果线性基中的元素
三.线性基的提升例题
- 题目描述: n个物品,每个物品的编号为正整数 ai,价值为正整数 vi,你可以选择若干个物品,但这些物品中,不能有相互异或得到 0 的情况,求最多能得到的价值为多少???
- 问题分析: 若 a1 xor a2 xor a3 …… xor am = 0,则这些物品中有一个物品不能被选。我们先对 vi 进行从大到小排序,然后考虑逐个选择物品,如果当前判断是否要选择的物品可放入就直接放入,否则就不放入。而且不可能因为一个元素而导致多个元素不能放入,就算去掉这个元素,也只能放入那多个元素中的一个。
struct node{
long long x,v;
}a[1000010];
int cmp(node x,node y){
return x.v>y.v;
}
long long b[100],ans=0;
void insert(long long x,long long value){
for(int i=62;i>=0;i--){
if((1ll<<i)&x){
if(b[i])x^=b[i];
else {
b[i]=x;
ans+=value;
return;
}
}
}
return;
}
int main(){
long long n,x,value;
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i].x>>a[i].v;
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)insert(a[i].x,a[i].v);
cout<<ans;
return 0;
}