zoukankan      html  css  js  c++  java
  • 魔法弹(线性基)

    魔法弹(线性基)

    给出n个m位数,问它们张成的异或集合的大小。

    关于线性基,这篇博文讲的太好了:

    所谓线性基,就是线性代数里面的概念。一组线性无关的向量便可以作为一组基底,张起一个线性的向量空间,这个基地又称之为线性基。这个线性基的基底进行线性运算,可以表示向量空间内的所有向量,也即所有向量可以拆成基底的线性组合。
    在ACM领域,线性基主要用来处理有关异或和的极值问题。根据异或按照二进制数位进行的方式,我们可以把一个数字拆成它的二进制表示形式,而这个二进制形式拆成一位一位的,可以用向量来表示。显然,一组线性无关的向量可以张起一个向量空间,我们同样可以考虑构造这样一组数字的二进制形式组成的线性基。在这个线性基里面,我可以通过基底的线性组合、异或运算,表示所有的异或结果。
    要构造这样的线性基,首先要满足一个性质:任取向量组中两个向量a、b,把其中一个替换成a xor b,这组向量线性组合得到的线性空间相同。这个性质对于xor来说是显然的,因为xor偶数具有抵消性。有了这个性质,就可以利用它对加入的向量进行修改。基本思路是:对于一个新加入的数字,从最高为考试往后扫,如果某一位该向量是1,但是之前的向量组中没有一个这一位是1,那么这一位就放如这个数字并且break;如果之前已经有该位为1的,那么该数字异或该位之前对应的数字。最后的结果是,新加入的数字x,要么被添加到某一个二进制位,要么变成0,说明这个数字对应的二进制向量不属于极大线性无关组。
    线性基构造完毕,我们就要求其极值。很多时候我们需要求一堆数字最大的异或和,这个时候就要用到线性基。线性基构造完毕之后,根据它的定义,任何线性基所张的向量空间的所有向量都能够表示为其线性组合。所以我们只需要找,这个线性基中最大的向量即可。做法就是,从高位开始取线性基的每一个基底,如果异或能够使得结果变大,那么就异或。同时,这个还支持有初始值的最大值,因为线性基可以表示所有的向量,所以即使有一个初始值也是能够一起计算最大值的。

    补充一下:n个的线性基能张成(2^n)个不同的元素,同时,有(2^{n-1})个元素满足第i位为1(当然前提是线性基中有第i位为1的数)。可以这样考虑:找到一个第i位为1的线性基x,那么剩下n-1个元素随便异或,能张成(2^{n-1})个数。为了让第i位为1,这些数中第i位为0的数需要异或x,否则就不用异或x。因此,这(2^{n-1})个数都一一对应一个第i位为1的数。

    对于这道题,尝试按位考虑。对于一个位,由于在线性基张成的空间中有(2^{n-1})个数这个位为1,因此它对总和的贡献是(x2^{n-1}),x表示这个位的权值。

    #include <cstdio>
    #include <cstring>
    using namespace std;
    
    typedef long long LL;
    const int maxn=405, mod=1e9+7;
    int n, m, A[maxn];
    struct Bigint{
        int a[maxn];  //从低到高
        bool empty(){
            int flag=1;
            for (int i=0; i<=m; ++i) if (a[i]) flag=0;
            return flag;
        }
        Bigint(){ memset(a, 0, sizeof(a)); }
        Bigint(char *s):Bigint(){
            for (int i=0; i<m; ++i) a[m-i-1]=s[i]-48;
            for (int i=0; i<m; ++i) A[i]|=a[i]; }
        int& operator[](int x){ return a[x]; }
        int value(){
            int re=0;
            for (int i=m; ~i; --i)
                re=(re*2+a[i])%mod;
            return re;
        }
    }p[maxn];
    Bigint operator ^(const Bigint &a, const Bigint &b){
        Bigint ans;
        for (int i=0; i<=m; ++i) ans[i]=a.a[i]^b.a[i];
        return ans; }
    
    int fpow(int a, int x){
        LL base=a, ans=1;
        for (; x; x>>=1, (base*=base)%=mod)
            if (x&1) (ans*=base)%=mod;
        return ans; }
    
    int main(){
        scanf("%d%d", &n, &m);
        char s[maxn]; Bigint t;
        for (int i=1; i<=n; ++i){
            scanf("%s", s);
            t=Bigint(s);
            for (int j=m; ~j; --j){
                if (!t[j]) continue;
                if (p[j].empty()) p[j]=t;
                t=t^p[j];
            }
        }
        LL ans=0, cnt=0, mi=1;
        for (int i=0; i<=m; ++i) if (!p[i].empty()) ++cnt;
        for (int i=0; i<=m; ++i){
            if (i) mi<<=1; mi%=mod;
            if (!A[i]) continue;
            ans+=fpow(2, cnt-1)*mi; ans%=mod;
        }
        printf("%lld
    ", ans);
        return 0;
    }
    
  • 相关阅读:
    windows wmi
    python编码规范
    gogs安装
    mariadb-5.5安装
    python kafka
    delimiter关键字
    phpstorm设置背景图片
    linux 下mysql 关闭 启动
    通过下载git包来安装git
    git clone 某个链接时候报错Initialized empty Git repository in 不能克隆
  • 原文地址:https://www.cnblogs.com/MyNameIsPc/p/9744980.html
Copyright © 2011-2022 走看看