• 线性基小结


    来源

    https://blog.sengxian.com/algorithms/linear-basis

    强烈推荐这篇文章,以下内容大部分来自于此。

    前言

    这篇是根据以上的博客再结合自己的浅薄理解写的,讲得并不详细,因为弱鸡本人还没完全懂,先学会使用吧。

    概述

    首先所谓的线性基,就是把若干个整数,看成 n 维 01 向量(通常 n 不会超过 64,即 long long 的范围),然后求这些向量的一个极大无关组。需要注意的一点是,对于这里的 01 向量,只有异或运算。

    下面是利用高斯消元求解线性基的代码

    void cal() {//a[i]为原来的数,b[i]为新的向量
        for (int i = 0; i < n; ++i)
            for (int j = MAX_BASE; j >= 0; --j)
                if (a[i] >> j & 1) {
                    if (b[j]) a[i] ^= b[j];
                    else {
                        b[j] = a[i];
                        for (int k = j - 1; k >= 0; --k) if (b[k] && (b[j] >> k & 1)) b[j] ^= b[k];
                        for (int k = j + 1; k <= MAX_BASE; ++k) if (b[k] >> j & 1) b[k] ^= b[j];
                        break;
                    }
    }

    对于最后得到的矩阵,如果第 的主对角线上为 1,此时我们称第 i 位存在于线性基中。

    例题

    SGU - 275

    给定 n(1n100000) 个数a1​​,a2​​,,an​​,请问这些数能够组成的最大异或和是什么?

    分析

    我们求出向量空间 V 的一组线性基。则答案就是将线性基中所有向量异或起来得到的向量所对应的数

    详细证明看最上面的链接。

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int MAX_BASE = 63;
    ll b[200];
    ll a[11234];
    void cal(int n) {
        for (int i = 0; i < n; i++) {
            for (int j = MAX_BASE; j >= 0; j--) {
                if (a[i] >> j & 1) {
                    if (b[j]) a[i] ^= b[j];
                    else {
                        b[j] = a[i];
                        for (int k = j - 1; k >= 0; k--) if (b[k] && b[j] >> k & 1) b[j] ^= b[k];
                        for (int k = j + 1; k <= MAX_BASE; k++) if (b[k] >> j & 1) b[k] ^= b[j];
                        break;
                    }
                }
            }
        }
    }
    int main() {
        int n;
        while (~scanf("%d", &n)) {
            memset(b, 0, sizeof(b));
            for (int i = 0; i < n; i++) {
                scanf("%lld", a + i);
            }
            cal(n);
            ll ans = 0;
            for (int i = 0; i <= MAX_BASE; i++) {
                if (b[i] >> i & 1) ans ^= b[i];
            }
            printf("%lld
    ", ans);
        }
    }

    HDU - 3949

    给定 n(n10000) 个数 a1​​,a2​​,,an​​,以及 Q(Q10000) 个询问,每次询问这些数(至少一个,不能不选)能够组成的异或和中第 k 小的数是什么(去掉重复的异或和)。

    分析

    我们求出向量空间 V 的一组线性基 B。因为向量空间中的任意向量u 均可以被表示称B 中向量的唯一线性组合,所以B 中的任意非空子集都可以构成一个向量且互不重复。所以这些数能够组成的异或和的个数为 2​^B​​1,特别的,如果 B<n,则必然存在一个向量组满足线性相关性,这个向量组也就能通过线性组合,异或得到 0,则异或和的个数为 2^B​​。

    假设线性基中有 m 个基向量,从小到大依次为 (v0​​,,vm1​​),则第 k=(bx​​b0​​)2​​ 小的数就是: ​(0ix​​)bi​​vi​​

    //  Created by Sengxian on 2016/12/5.
    //  Copyright (c) 2016年 Sengxian. All rights reserved.
    //  HDOJ 3949 线性基
    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long ll;
    inline ll readLL() {
        static ll n;
        static int ch;
        n = 0, ch = getchar();
        while (!isdigit(ch)) ch = getchar();
        while (isdigit(ch)) n = n * 10 + ch - '0', ch = getchar();
        return n;
    }
    
    const int MAX_N = 100000 + 3, MAX_BASE = 60;
    int n, zero = false;
    ll a[MAX_N], b[MAX_BASE + 3];
    vector<ll> mmap;
    
    void prepare() {
        int cnt = 0;
        memset(b, 0, sizeof b);
        for (int i = 0; i < n; ++i)
            for (int j = MAX_BASE; j >= 0; --j)
                if (a[i] >> j & 1) {
                    if (b[j]) a[i] ^= b[j];
                    else {
                        b[j] = a[i], cnt++;
                        for (int k = j - 1; k >= 0; --k) if (b[k] && ((b[j] >> k) & 1)) b[j] ^= b[k];
                        for (int k = j + 1; k <= MAX_BASE; ++k) if ((b[k] >> j) & 1) b[k] ^= b[j];
                        break;
                    }
                }
        zero = cnt != n;
    
        mmap.clear();
        for (int i = 0; i <= MAX_BASE; ++i)
            if (b[i]) mmap.push_back(b[i]);
    }
    
    ll query(ll k) {
        if (zero) k--;
        if (k >= (1LL << (int)mmap.size())) return -1;
        ll ans = 0;
        for (int i = 0; i < (int)mmap.size(); ++i) if ((k >> i) & 1)
            ans ^= mmap[i];
        return ans;
    }
    
    int main() {
    #ifdef DEBUG
        freopen("test.in", "r", stdin);
    #endif
        int caseNum = readLL();
        for (int t = 1; t <= caseNum; ++t) {
            n = readLL();
            for (int i = 0; i < n; ++i) a[i] = readLL();
    
            prepare();
    
            printf("Case #%d:
    ", t);
            int q = readLL();
            for (int i = 0; i < q; ++i) printf("%lld
    ", query(readLL()));
        }
        return 0;
    }

    小结

    线性基的题型相对比较固定,看到下面的类型基本上都是线性基了:

    1. 最大异或和
    2. 第 kk 大异或和/异或和是第几大
    3. 求所有异或值的和
  • 相关阅读:
    2009中国IT界名人
    jQuery简介
    Spring下载地址
    ContextLoaderListener
    MyBatisUtil类
    SSM事务
    后台管理中心跳转问题解决
    mybatis返回boolean值时数据库返回null
    yarn作业提交过程
    Hadoop集群运行wordcount jar包出错
  • 原文地址:https://www.cnblogs.com/fht-litost/p/9562807.html
Copyright © 2020-2023  润新知