线性基——数集压缩自动机

参考:

[学习笔记]线性基

线性基学习笔记

线性基(linear basis??)

介绍:

  基:在线性代数中,基(也称为基底)是描述、刻画向量空间的基本工具。向量空间的基是它的一个特殊的子集,基的元素称为基向量。向量空间中任意一个元素,都可以唯一地表示成基向量的线性组合。如果基中元素个数有限,就称向量空间为有限维向量空间,将元素的个数称作向量空间的维数。
  同样的,线性基是一种特殊的基,它通常会在异或运算中出现,它的意义是:通过原集合S的某一个最小子集S1使得S1内元素相互异或得到的值域与原集合S相互异或得到的值域相同。
 

性质:

  1. 线性基能相互异或得到原集合的所有相互异或得到的值。
  2. 线性基是满足性质1的最小的集合
  3. 线性基没有异或和为0的子集(显然,当a1^a2^..^an=0时,a1=a2^...^an a1是可以通过其他元素表出的,没有用,与性质2矛盾)

(摘自:百度百科)

简单来说,线性基的本质是通过最少的数(a1,a2...an),能够表示出所有原来的数所能表示出的数。

这个{an}数集就是线性基。

可以这样理解,线性基就是对于一个数集的压缩。

举个例子:a,b,c 且 a^b=c

那么a,b,c能通过异或表示出的所有数,a,b通过异或都能表示。也可以得出,c在异或意义下可以由a,b表出

 

一:

基于以上的理解和性质,

线性基最经典的应用是可以处理异或和最大问题。

也就是,给出若干个数,从中选择若干个数,使得它们的异或和最大

(胡乱贪心:

1.选择一个数开始,如果能^后增大答案就选上。

反例:1010,1111 假设会选择1010,就选不上1111了。

2.那我先从大到小排个序考虑?

反例:11110,10101,01010,答案是10101^01010但是会选择11110

正解:

 

因为,a^b^b=a,a^0=a

所以,1111,1101,10,1,110,1001,这些个数,都能用1000,100,10,1通过异或表出。

所以,随便异或异或异或爱咋滴咋滴,反正都能异或回去。

这是一个直观的考虑,我们也可以不必都变成2^k

我们只要每个最高位的1都留下一个就好了。

具体方法如下:

p[j] 从小到大第j位为1的第一个数值(不一定是原始的数,可能通过异或得出)

for 向线性基中插入所有的数,

   for 最高位->最低位 每一位

  if 这一位没有

   p[j]=x;退出

  else x^=a[p[j]] 去掉这一位

这样,每个插入的数只有两个结局:要么进入线性基,要么变成0

查最大值时,直接贪心地从最高位的p[j]开始。

能增大ret就选上。

(证明:

因为这一位为1的仅有这一个数。

若^后ret不变大,说明ret这一位一定是1

若^后ret变大,说明这一位一定是0,而却不选,那么之后ret一定只能为0了,不优。

代码:luogu P3812 【模板】线性基

#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
const int N=55;
int n;
struct bas{
    ll a[N];
    void insert(ll x){
        for(int i=55;i>=0;i--){
            if(x&(1LL<<i)){
                if(!a[i]){
                    a[i]=x;return;
                }
                x^=a[i];
            }
        }    
    }
    ll query(){
        ll ret=0;
        for(int i=55;i>=0;i--){
            if((ret^a[i])>ret) ret^=a[i];
        }
        return ret;
    }
}lb;
int main()
{
    scanf("%d",&n);ll t;
    for(int i=1;i<=n;i++){
        scanf("%lld",&t);lb.insert(t);
    }printf("%lld",lb.query());return 0;
}
P3812 【模板】线性基

 

二:

另外,线性基还能处理别的问题:

例题:

[JLOI2015]装备购买

题意:

n 个装备,每个装备 m 个属性,每个装备还有个价格。如果手里有的装备的每一项属性为它们分配系数(实数)后可以相加得到某件装备,则不必要买这件装备。求最多装备下的最小花费。求装备个数及最小花费。

也就是说,每个物品是一个m维向量,选择出来的物品,不能线性相关。

(补充线性相关:存在不全为0的实数k1,k2,...kn,使得a1k1+a2k2+...+ankn=0 (其中a1~an是若干维的向量)则这些向量线性相关,否则线性无关

换句话说,如果存在一个向量可以通过其他的向量线性表出,那么这些向量就线性相关。

 

发现,线性相关问题和异或问题的相似点在于:

如果一些向量{a}可以表出的所有向量是{b},那么,{a}中向量的各种线性运算后,还是能表示出{b},不多不少

所以,可以仿照之前的p[j],

定义:p[j]表示,第j维向量里,第一个不为0的物品编号(该物品可能已经通过线性运算改变了属性值)

显然为了话费最小,sort按照cost处理

for 所有物品

 for 该物品所有属性

    if 这个属性维不是0{

  if p[j]没有

   p[j]=i ,sum+=cost,cnt++;break

  else

           像高斯消元一样,消除这一维,后面所有维(前面可以不用管,因为之后给别的消也用不上)

   这个物品的属性也随之改变,没有关系,因为线性改变不会影响线性表出结果。

  }

对于每一个物品,有两种结果:

1.消除到一半,发现p[j]没有,加入线性基,退出。

2.消除完了,最后各属性都是0了。相当于可以通过之前加入的所有物品线性表出。不能加入。

(不会存在p[j]都满了,但是消完m位后,有一位不是0的情况。

因为之前选择的m个向量彼此之间线性无关,可以作为m维空间的基底,线性表示所有的向量了。

换句话说,加入了最多m个向量后,答案就出来了。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long double lb;
const int N=500+10;
const lb eps=1e-8;
int n,m;
struct node{
    int cos;
    lb x[N];
}a[N];
int p[N];
bool cmp(node a,node b){
    return a.cos<b.cos;
}
int cnt,sum;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++) scanf("%Lf",&a[i].x[j]);
    }
    for(int i=1;i<=n;i++) scanf("%d",&a[i].cos);
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(fabs(a[i].x[j])>eps){
                if(!p[j]){
                    p[j]=i;cnt++;sum+=a[i].cos;break;
                }
                else{
                    lb t=a[i].x[j]/a[p[j]].x[j];
                    for(int k=j;k<=m;k++) a[i].x[k]-=t*a[p[j]].x[k];
                }
            }
        }        
    }
    printf("%d %d",cnt,sum);return 0;
}
JLOI 装备购买

 

为什么是对的?

 因为线性运算对于线性表出没有影响啊。

我们把它减去了若干倍,还是可以的。

那么我们相当于是怎样实现的线性表出的验证呢??

  比如:三个物品a0,b0,c0,

  线性基之前加入了a0,b1, b1=b0-k0a0

  对于c0,我们会验证:

  它是不是a0“倍数”? c0-=k1*a0 == 0?

  它是不是可以用a0,b0表出? c0-=k1*a0 + k2* b1 ==0 ?

    ( 若ax+by=z, a,b有实数解,那么,a'x+b'(y-k*x)=z 即: (a'-k)x + b'*y =z 对于a',b'也有实数解。)

  它是不是可以用b0表出? c0-=k2*b1==0?假设c0是b0的p倍,

因为,b1是减去k0倍a0,所以,c0在a0的时候,会减去pk0的a0,减完之后,c0还是b1的倍数。

另外,因为我们贪心按照cost排序,所以先插入的一定是最优的价值。

 

 

总结:

线性基通过提取数集中最少的元素,可以办到原数集中所有的数可以办到的事。

线性基:数集压缩自动机

posted @ 2018-07-18 11:05  *Miracle*  阅读(379)  评论(0编辑  收藏  举报