当前位置: 首页 > article >正文

线性基速通

1. 引入线性基的问题:
  • 一些数随意选取,求可以异或出的第k大数。单纯枚举所有情况的话是 O ( 2 n ) O(2^n) O(2n),引入线性基后,可以把时间复杂度降低到 O ( n l o g n ) O(nlogn) O(nlogn)
2. 什么是线性基&&为什么线性基可以降低时间复杂度:
  • 把一堆数以二进制的形式纵向排列,形成了 n ∗ 64 n*64 n64 01 01 01 矩阵,由于矩阵的秩 r ( A ) ≤ m i n ( n , 64 ) r(A)\leq min(n,64) r(A)min(n,64)
  • 可以证明这些数字组成的矩阵一定可以找到大小小于等于 64 64 64 Z 2 {Z}_2 Z2 域中的线性无关组。( 在 Z 2 {Z}_2 Z2 中的加法为异或,乘法为与,可以证明 Z 2 {Z}_2 Z2 是域。)
  • 找到线性无关组之后,一个简单的想法就是枚举所有向量的组合,实际上起作用的向量只有64个, O ( 2 64 ) O(2^{64}) O(264) 就可以找到所有的情况了,这样的线性无关组就是线性基。实际上,如果只需要寻找一个数可不可以被构成,可以达到 O ( l o g n ) O(logn) O(logn) 的级别。
3. 如何实现寻找线性基等函数
  • 贪心构造出向量组的线性基:
    void insert(int x){//插入一个数x
        for(int i=63;i>=0;i--){
            if(x&(1ll<<i)){
                if(p[i]) x^=p[i];
                else {
                    p[i]^=x;
                    cnt++;
                    break;
                }
            }
        }
    }
    
    贪心地选取每一位,如果这一位已经有了,就把它异或掉,可以证明到最后一定是一组基。
    这样构造出来的线性基, p [ i ] p[i] p[i] 一定是表示首位为 1 1 1 的基,而且不会重复,插入单个元素复杂度 O ( l o g n ) O(logn) O(logn)
  • 寻找最大数:
    int askmx(){
        int x=0;
        for(int i=63;i>=0;i--){
            if((x^p[i])>x) x^=p[i];
        }
        return x;
    }
    
    从高位到低位枚举,贪心选取最高位一定是对的。
  • 寻找最小数:
      int askmn(){
          int mn=inf;
          if(cnt==0||n>cnt) return 0;
          else{
              for(int i=0;i<=64;i++){
                  if(p[i]) mn=min(mn,p[i]);
              }
              return mn;
          }
      }
    
    一般来说,最小值就是贪心构造出来的线性基的某个值,但是需要考虑一个特殊情况——几个数异或起来变成了 0 0 0。这种情况只需要判断插入的数字的数量和这些数字构造出的基的数量,如果有数字没有成为基,那么它一定可以被其它基线性表出,这种情况会异或出0。
  • 查询是否可以构造出某个数:
    bool ask(int x){//查询是否有一个数字
        for(int i=63;i>=0;i--){
            if(x&(1ll<<i)){
                if(p[i]) x^=p[i];
            }
        }
        return (x==0);
    }
    
    从大到小异或,这样如果最后异或为 0 0 0,代表可以线性表出,不为零代表不行。
  • 求第 k k k 大数:
    void rebuild() {
    	cnt=0;
    	for(int i=63;i>=0;i--)
    		for(int j=i-1;j>=0;j--)
    			if(p[i]&(1LL<<j)) p[i]^=p[j];
    	for(int i=0;i<=63;i++) if(p[i]) d[cnt++]=p[i];
    }
    int kth(int k) {
    	if(k>=(1LL<<cnt)) return -1;
    	int ans=0;
    	for(int i=63;i>=0;i--)
    		if(k&(1LL<<i)) ans^=d[i];
    	return ans; 
    }
    
    原来的线性基不满足求第 k k k 大的要求,不满足行阶梯型,即不会有两个线性基共享其中一个的最高位,如 10110 10110 10110 00100 00100 00100 ,不是一组完美的线性基。需要通过高斯消元求阶梯形。
    求完之后,可以直接贪心选取第k个数。
    这里还需要判断 0 0 0 的情况,如果会凑出 0 0 0 ,查询位置要递推 1 1 1
    for(int i=0;i<64;i++) p[i]=0;
    int n;cin>>n;
    for(int i=1;i<=n;i++){
        int x;cin>>x;
        insert(x);
    }
    rebuild();
    int m;cin>>m;
    if(n>cnt) zero=1;
    for(int i=1;i<=m;i++){
        int q;cin>>q;
        if(n>cnt&&q==1) cout<<0<<endl;
        else cout<<kth(q-zero)<<endl;
    }
    
4. 线性基一些性质:
  • Q: 有 n n n 个元素,每个元素有个序号和一个值,一个元素可以选择当且尽当其序号与已选元素序号的异或和不为 0 0 0,求你可选择的元素值和的最大值
  • A: 贪心地将最大值元素的序号塞入线性基中,如果插入失败,就不选择这个元素。这样插入的结果一定是最大值。
  • 性质:如果一个元素不能插入线性基中(可以被其它线性基线性表出),一定可以只删掉一个元素,把它插入线性基。
5. 一道测试题:
  • https://codeforces.com/gym/105336/attachments
  • a i a_i ai b i b_i bi 的值异或起来,插入线性基,然后按位分类讨论贪心即可。

http://www.kler.cn/a/309080.html

相关文章:

  • 【OH】openHarmony开发环境搭建(基于windows子系统WSL)
  • 【C++】一种针对代码的连续条件检查方案,累计布尔结果
  • 图片画廊 day2 (可复制源码)
  • 系统上线后发现bug,如何回退版本?已经产生的新业务数据怎么办?
  • Web大学生网页作业成品——婚礼婚纱网页设计与实现(HTML+CSS)(6个页面)
  • Linux kernel 堆溢出利用方法(二)
  • 哪款宠物空气净化器是除浮毛王者?希喂、范罗士、霍尼韦尔宠物空气净化器实测
  • 爬坑--docker构建容器ssh连接容器环境变量会发生变化
  • Redis的IO模型
  • 计算机网络分层结构解析:OSI与TCP/IP模型
  • Blender渲染太慢怎么办?blender云渲染已开启
  • 在设计开发中,如何提高网站的用户体验?
  • Qt开发技巧(四)“tr“使用,时间类使用,Qt容器取值,类对象的删除,QPainter画家类,QString的转换,用好 QVariant类型
  • Vite项目中eslint的简单配置
  • Amazon 正式官宣取消居家上班(WFH)
  • Ubuntu apt 命令全面讲解
  • 行业机遇!程序员:如何选择适合自己的就业方向?
  • linux--防火墙
  • 【Android】处理线程中未捕获的异常
  • python加载chgcar, aeccar压缩数据
  • FRP之简单粗暴官方搭建【超详细教程】【排坑】【包括官网下载地址】【伸手党福利】
  • 容器镜像同步工具image-migrator
  • 第14章 存储器的保护
  • Linux网络子系统TCP篇 二
  • 【PostgreSQL里vacuum但是无法回收死元组的原因】
  • 解决 Docker 端口映射错误:“No public port ‘80’ published”