线性基速通

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 的值异或起来,插入线性基,然后按位分类讨论贪心即可。
线性可以用来判断原集合是否封闭。如果一个元素能够被线性向量线性表示,那么它就可以由原集合中的元素经过线性组合得到,即原集合是封闭的。否则,如果有一个元素不能被线性向量线性表示,那么它就无法由原集合中的元素经过线性组合得到,即原集合不是封闭的。 具体地,我们可以通过将待判断的元素与线性向量进行异或操作来判断是否能够线性表示。如果待判断元素与线性向量进行异或操作后得到零向量,则说明待判断元素可以由线性向量线性表示。如果待判断元素与线性向量进行异或操作后得到非零向量,则说明待判断元素无法由线性向量线性表示。 因此,我们可以通过判断待判断元素与线性向量进行异或操作的结果是否为零向量来判断原集合是否封闭。如果待判断元素与线性向量进行异或操作后都得到零向量,则原集合是封闭的;否则,原集合不是封闭的。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [线性模板](https://blog.csdn.net/weixin_43519854/article/details/96977900)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [【矩阵论】线性空间与线性变换(3)(4)](https://blog.csdn.net/kodoshinichi/article/details/108916238)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值