zoukankan      html  css  js  c++  java
  • 线性基小结

    来源

    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. 求所有异或值的和
  • 相关阅读:
    HDU 4348 To the moon(可持久化线段树)
    HDU 5875 Function 大连网络赛 线段树
    HDU 5877 2016大连网络赛 Weak Pair(树状数组,线段树,动态开点,启发式合并,可持久化线段树)
    HDU 5876 大连网络赛 Sparse Graph
    HDU 5701 中位数计数 百度之星初赛
    CodeForces 708B Recover the String
    Java实现 蓝桥杯 算法提高 套正方形(暴力)
    ASP.NET生成验证码
    ASP.NET生成验证码
    ASP.NET生成验证码
  • 原文地址:https://www.cnblogs.com/fht-litost/p/9562807.html
Copyright © 2011-2022 走看看