zoukankan      html  css  js  c++  java
  • 线性基——数集压缩自动机

    参考:

    [学习笔记]线性基

    线性基学习笔记

    线性基(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排序,所以先插入的一定是最优的价值。

    总结:

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

    线性基:数集压缩自动机

  • 相关阅读:
    解释器模式
    命令模式
    责任链模式
    代理模式
    享元模式
    外观模式
    装饰器模式
    组合模式
    过滤器模式
    js广告浮动
  • 原文地址:https://www.cnblogs.com/Miracevin/p/9328017.html
Copyright © 2011-2022 走看看