线性基学习小记

谢谢hzj的PPT


OI中的线性基一般的指的不是向量,而是异或。

最简单的理解就是用最少的数去表示一堆数的异或集合。


维护一个一维数组,每个元素记录这个位存储的数。

设当前要加入的数为x,从高位到低位扫,当前扫到2^i这一位,存储的数为a[i]
若x的2^i位为1,

若a[i]=0,那么a[i]=x,并用a[0~i-1]消掉a[i]的2^(0~n-i-1)二进制位(能消就消),再用x把a[i+1~n]的2^i位消掉。

若a[i]!=0,则x=x xor a[i]。


一般的线性基没有回消这个操作,在求一些东西的时候需要特判。


Code:

void ins(ll *a, ll x) {
    fd(w, 62, 0) {
        if(x >> w & 1) {
            if(!a[w]) {
                a[w] = x; cnt ++;
                fo(i, 0, w - 1) if(a[w] >> i & 1) a[w] ^= a[i];
                fo(i, w + 1, 62) if(a[i] >> i & 1) a[i] ^= a[w];
                break;
            } else x ^= a[w];
        }
        if(!x) {bx0 = 1; break;}
    }
}

因为线性基默认没有0,所以异或出0有需求的话记录。

线性基的用法:

1.异或集合的大小: 2 2 基 的 个 数

2.异或集合的最大值:把基里的数全部异或起来

3.异或集合的最小值:位最小的基

4.去重第k小数:
把k二进制拆分,然后对应选基异或起来即可。

5.一个数如果能被异或出来,则方案数是 2 2 总 数 − 基 的 个 数

题目:
bzoj 4671: 异或图

这个是求异或出0的方案数,可以用线性基,也可以用高斯消元。

Code:

/**************************************************************
    Problem: 4671
    User: a2317757009
    Language: C++
    Result: Accepted
    Time:10812 ms
    Memory:1320 kb
****************************************************************/

#include<bits/stdc++.h>
#define ll long long
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define fd(i, x, y) for(int i = x; i >= y; i --)
#define fu(a) ((a) & 1 ? -1 : 1)
using namespace std;

char str[101]; ll b[61];
int s, n, bz[11], cnt, a[61][11][11], num[11], tn[46];
ll fac[11], ans;

struct node {
    int x, y;
} e[46];

void dg(int x, int y) {
    if(x > n) {
        cnt = 0;
        fo(i, 1, n) fo(j, i + 1, n) {
            if(bz[i] == bz[j]) continue;
            ll cur = 0;
            fo(k, 1, s) if(a[k][i][j]) cur |= 1LL << k;
            fo(k, 1, cnt) if((cur ^ b[k]) < cur) cur ^= b[k];
            if(cur) b[++ cnt] = cur;
        }
        ans += fu(y - 1) * (1LL << (s - cnt)) * fac[y - 1];
        return;
    }
    fo(i, 1, y) bz[x] = i, dg(x + 1, y);
    bz[x] = y + 1; dg(x + 1, y + 1);
}

int main() {
    fac[0] = 1; fo(i, 1, 10) fac[i] = fac[i - 1] * i;
    fo(i, 1, 10) num[i] = i * (i - 1) / 2, tn[num[i]] = i;
    scanf("%d", &s);
    fo(ii, 1, s) {
        scanf("%s", str + 1); n = tn[strlen(str + 1)];
        int p = 0;
        fo(i, 1, n) fo(j, i + 1, n) a[ii][i][j] = str[++ p] - '0';
    }
    dg(1, 0);
    printf("%lld", ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值