线性基的三大性质:
线性基能相互异或得到原集合的所有相互异或得到的值。
线性基是满足性质1的最小的集合。
线性基没有异或和为0的子集。
模板:
const int MaxBasis = 62;///二进制位数
struct LB {
ll base[MaxBasis+10]; bool rel; int sz;
vector<ll> Basis;/// 线性基(向量)
LB() { memset(base, 0, sizeof(base)); rel = false; sz = 0; Basis.clear();}
void init() {
memset(base,0,sizeof(base));rel = false;sz = 0;Basis.clear();
}
int add(ll x) { ///加入线性基中
for (int i = MaxBasis; i >= 0; --i) {
if (!(x >> i & 1)) continue;
if (base[i]) x ^= base[i];
else {
for (int j = 0; j < i; ++j) if (x >> j & 1) x ^= base[j];
for (int j = i+1; j <= MaxBasis; ++j) if (base[j] >> i & 1) base[j] ^= x;
base[i] = x, ++sz;
return 1;
}
}
rel = true;
return 0;
}
ll Max(ll ans = 0) { ///取最大
for(int i=0; i<=MaxBasis;i++) ans = max(ans,ans^base[i]);
return ans;
}
ll Min(ll ans = 0) { ///取最小
for(int i=0; i<= MaxBasis;i++) ans = min(ans,ans^base[i]);
return ans;
}
void GetBasis() { ///构造向量,用于之后求第k小
for (int i = 0; i <= MaxBasis; ++i)
if (base[i]) Basis.push_back(base[i]);
}
void bin(struct LB &b) { /// 线性基求并(合并)
for(int i=0;i <= MaxBasis;i++) if(b.base[i])
add(b.base[i]);
}
LB jiao(LB a,LB b){ /// 线性基求交
LB g,tmp = a;
ll cur,d;
for(int i = 0;i <= MaxBasis;i++) if(b.base[i]){
cur = 0,d = b.base[i];
for(int j = i;j >= 0;j--) if(d>>j&1){
if(tmp.base[j]){
d ^= tmp.base[j],cur ^= a.base[j];
if(d) continue;
g.base[i] = cur;
}else tmp.base[j] = d, a.base[j] = cur;
break;
}
}
return g;
}
ll Min_Kth(ll k) { /// 线性基中第k小
if(rel) k--; /// 线性基未满存在0
if(k >= ((ll)1<<sz)) return -1;
ll ans = 0;
for(int i=0;i<sz;i++) if(k & ((ll)1<<i)){
ans ^= Basis[i];
}
return ans;
}
};
基本应用有求子集异或的最大(小)值、第k大,线性基合并和求交集,模板里面都有。具体讲解可以参考https://blog.csdn.net/a_forever_dream/article/details/83654397。
1、BJWC 2011 元素
题意:有n个东西,每个东西能选当且仅当它的元素序号不能通过之前选过的东西的元素序号异或得到,在此前提下,求魔力的最大值。
分析:按魔力从大到小排序,贪心选取魔力值大的,插入线性基,证明见https://www.luogu.org/problemnew/solution/P4570。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e6+5;
const int MaxBasis = 62;///二进制位数
struct LB {
ll base[MaxBasis+10]; bool rel; int sz;
vector<ll> Basis;/// 线性基(向量)
LB() { memset(base, 0, sizeof(base)); rel = false; sz = 0; Basis.clear();}
void init() {
memset(base,0,sizeof(base));rel = false;sz = 0;Basis.clear();
}
int add(ll x) { ///加入线性基中
for (int i = MaxBasis; i >= 0; --i) {
if (!(x >> i & 1)) continue;
if (base[i]) x ^= base[i];
else {
for (int j = 0; j < i; ++j) if (x >> j & 1) x ^= base[j];
for (int j = i+1; j <= MaxBasis; ++j) if (base[j] >> i & 1) base[j] ^= x;
base[i] = x, ++sz;
return 1;
}
}
rel = true;
return 0;
}
ll Max(ll ans = 0) { ///取最大
for(int i=0; i<=MaxBasis;i++) ans = max(ans,ans^base[i]);
return ans;
}
ll Min(ll ans = 0) { ///取最小
for(int i=0; i<= MaxBasis;i++) ans = min(ans,ans^base[i]);
return ans;
}
void GetBasis() { ///构造向量,用于之后求第k小
for (int i = 0; i <= MaxBasis; ++i)
if (base[i]) Basis.push_back(base[i]);
}
void bin(struct LB &b) { /// 线性基求并(合并)
for(int i=0;i <= MaxBasis;i++) if(b.base[i])
add(b.base[i]);
}
LB jiao(LB a,LB b){ /// 线性基求交
LB g,tmp = a;
ll cur,d;
for(int i = 0;i <= MaxBasis;i++) if(b.base[i]){
cur = 0,d = b.base[i];
for(int j = i;j >= 0;j--) if(d>>j&1){
if(tmp.base[j]){
d ^= tmp.base[j],cur ^= a.base[j];
if(d) continue;
g.base[i] = cur;
}else tmp.base[j] = d, a.base[j] = cur;
break;
}
}
return g;
}
ll Min_Kth(ll k) { /// 线性基中第k小
if(rel) k--; /// 线性基未满存在0
if(k >= ((ll)1<<sz)) return -1;
ll ans = 0;
for(int i=0;i<sz;i++) if(k & ((ll)1<<i)){
ans ^= Basis[i];
}
return ans;
}
};
int n;
struct nd {
ll id,w;
}p[N];
bool cmp(nd x,nd y) {
return x.w>y.w;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld%lld",&p[i].id,&p[i].w);
sort(p+1,p+1+n,cmp);
LB lb;
ll ans(0);
for(int i=1;i<=n;i++)
if(lb.add(p[i].id)) ans += p[i].w;
printf("%lld\n",ans);
return 0;
}
2、SCOI 2016 幸运数字
题意:求树上路径点权集合子集异或的最大值。
分析:线性基合并模板题,倍增的思想,和fa数组一样的道理,还是求出lca分情况讨论的套路。
代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e4 + 10;
ll a[N],b[N][20][70],dep[N],ans[70],fa[N][20];
vector<int> g[N];
ll read() {
ll x = 0, f = 1;
char ch = getchar();
while(ch>'9'||ch<'0'){
if (ch == '-') f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
x = x * 10 + ch-'0';
ch = getchar();
}
return x * f;
}
void add(ll B[],ll x) {
for (int i = 63; i >= 0; i--) {
if (x & (1ll<<i)) {
if (B[i]) x^=B[i];
else {
B[i] = x;
break;
}
}
}
}
void mg(ll B1[],ll B2[]){
for (int i = 63; i >= 0; i--)
if (B2[i]) add(B1,B2[i]);
}
void dfs(int f,int u) {
fa[u][0] = f;
dep[u] = dep[f] + 1;
for (int i = 1; i <= 15; i++) {
fa[u][i] = fa[fa[u][i-1]][i-1];
mg(b[u][i], b[u][i-1]);
mg(b[u][i] ,b[fa[u][i-1]][i-1]);
}
for (int i = 0; i < g[u].size(); i++) {
if (f == g[u][i]) continue;
dfs(u,g[u][i]);
}
}
void lca(int x,int y) {
memset(ans,0,sizeof(ans));
if (dep[x] < dep[y]) swap(x,y);
for (int i = 15; i >= 0; i--)
if (dep[fa[x][i]] >= dep[y]) {
mg(ans,b[x][i]);
x = fa[x][i];
}
if (x == y) {
mg(ans,b[x][0]);
return;
}
for (int i = 15; i >= 0; i--)
if (fa[x][i] != fa[y][i]) {
mg(ans,b[x][i]);
mg(ans,b[y][i]);
x = fa[x][i];
y = fa[y][i];
}
mg(ans,b[x][0]);
mg(ans,b[y][0]);
mg(ans,b[fa[x][0]][0]);
}
int main() {
ll n = read(),q = read(),x,y;
for (ll i = 1; i <= n; i++)
add(b[i][0],a[i] = read());
for(ll i = 1;i < n; i++){
x = read(),y = read();
g[x].push_back(y);
g[y].push_back(x);
}
dfs(0,1);
while(q--) {
x = read(),y = read();
lca(x,y);
ll res = 0;
for (int i = 63; i >= 0; i--)
res = max(res,res^ans[i]);
printf("%lld\n", res);
}
return 0;
}
3、TJOI 2008 彩灯
题意:m个开关,每个开关控制一些灯泡,求出现的不同局面数。(我们可以看成一个0/1串,初始是一个全部为0的串,要求经过这些开关的操作后,出现的不同的0/1串的个数)
分析:开关就相当于异或操作,这是使用线性基的前提,知道这个的话这题就是个线性基的模板题了,证明见https://www.luogu.org/problemnew/solution/P3857。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=51,mod=2008;
int cnt;
ll arr[N];
void add (ll box)
{
for(int i=50; i>=0; i--)
{
if(!(box>>i&1)) continue;
if(!arr[i])
{
++cnt,arr[i]=box;
break;
}
else box^=arr[i];
}
}
int n,m;
char s[N];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1; i<=m; i++)
{
scanf("%s",s);
int len=strlen(s);
ll x=0;
for(int i=0; i<len; i++) x+=(1ll<<(n-i))*(s[i]=='O');
add(x);
}
printf("%lld\n",(1ll<<cnt)%mod);
return 0;
}