谢谢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
#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);
}