个人理解:线性基有点类似于矩阵中向量组的最大无关组,一个数集所有可能的异或结果是数集。在数集中,选出若干个数构成集合,使得中的每一个数字都可以由中的若干元素通过异或来表示,选出的集合就是数集的线性基(也是的线性基)。
选出集合S的过程通过以下函数来完成:
ll b[70];
void add(ll x){
for(int i = 63; i >= 0 && x;i--)
if((x>>i)&1){
if(b[i] == 0){
b[i] = x; return;
}
else x ^= b[i];
}
}
将中的每个元素使用add函数调用。得到数组b,数组b就是集合的线性基,其中b[i]不为0的位置表示该位置是任意可随意设置为0或1的(异或结果共2^count个值,count=Σ[b[i]!=0])。
引入问题1:P3812 【模板】线性基
题意:在一堆数种选出若干个数,使得选出来的数异或起来最大。
解决方案:找出线性基,对线性基能表示的高位尽量表示出来,由高位向低位枚举贪心即可。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n; ll a[70], b[70], ans = 0;
void add(ll x){
for(int i = 63; i >= 0 && x;i--)
if((x>>i)&1){
if(b[i] == 0){
b[i] = x; return;
}
else x ^= b[i];
}
}
int main(){
scanf("%d",&n);
for(int i = 1;i<=n;i++)
scanf("%lld",&a[i]), add(a[i]);
for(int i = 63;i>=0;i--)
if(b[i])ans = max(ans,ans ^ b[i]);
printf("%lld\n",ans);
return 0;
}
应用问题2:P4301 [CQOI2013]新Nim游戏
题意:在第一个回合中,第一个游戏者可以直接拿走若干个整堆的火柴。可以一堆都不拿,但不可以全部拿走。第二回合也一样,第二个游戏者也有这样一次机会。从第三个回合(又轮到第一个游戏者)开始,规则和Nim游戏一样。
如果你先拿,怎样才能保证获胜?如果可以获胜的话,还要让第一回合拿的火柴总数尽量小。
解决方案:使得对方操作完剩余的数一定不为0,那么说明留给对方可操作的数一定线性无关,也就是留给对方一组线性基即可
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n; ll a[500], b[70], sum = 0;
bool add(ll x){
for(int i = 62; i >= 0 && x;i--){
if((x>>i)&1){
if(b[i] == 0){
b[i] = x; return 1;
}
else x ^= b[i];
}
}
return 0;
}
int main(){
scanf("%d",&n);
for(int i = 1;i<=n;i++)
scanf("%lld",&a[i]), sum += a[i];
sort(a+1,a+n+1,greater<ll>());
for(int i = 1;i<=n;i++)
if(add(a[i]))sum -= a[i];
printf("%lld\n",sum);
return 0;
}
应用问题3:P4151 [WC2011]最大XOR和路径
题意:考虑一个边权为非负整数的无向连通图,节点编号为 1 到 N,试求出一条从 1 号节点到 N 号节点的路径,使得路径上经过的边的权值的 XOR 和最大。路径可以重复经过某些点或边,当一条边在路径中出现了多次时,其权值在计算 XOR 和时也要被计算相应多的次数。
解决方案:先任意找到一条路径从1能够到达n,那么在这条路径上有若干个环可以让我们任意地选择走与不走,如果知道包含所有边的环的异或值,就是上面裸的问题1了,对于一条边(u,v),我们可以走 1 -> u -> v -> 1 的环,或是不走,这样对所有的边都有一个环,那么这些环全部都可以选择走与不走了,转化为问题1,AC
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n, m;
struct edge{
ll w;
int v, next;
}e[200005];
int head[50005],cnt;
void add(int u, int v, ll w){
e[cnt].v = v, e[cnt].w = w, e[cnt].next = head[u], head[u] = cnt++;
}
ll dis[50005];
bool vis[50005];
ll b[64];
void addd(ll x){
for(int i = 63;i>=0 && x;i--){
if((x>>i)&1){
if(!b[i]){
b[i] = x;
return;
}else{
x ^= b[i];
}
}
}
}
int main(){
memset(head,-1,sizeof head);
scanf("%d%d",&n,&m);
for(int i = 1;i<=m;i++){
int u, v; ll w;
scanf("%d %d %lld",&u,&v,&w);
add(u,v,w); add(v,u,w);
}
queue<int>que;
que.push(1); vis[1] = 1;
while(que.size()){
int u = que.front(); que.pop();
for(int i = head[u];~i;i = e[i].next){
if(!vis[e[i].v]){
vis[e[i].v] = 1;
que.push(e[i].v);
dis[e[i].v] = (dis[u] ^ e[i].w);
}
}
}
for(int i = 0;i<m;i++)
addd(dis[e[i<<1|1].v]^dis[e[i<<1].v]^e[i<<1].w);//1->u->v->1的异或和
ll ans = dis[n];
for(int i = 63;i>=0;i--)
ans = max(ans, ans ^ b[i]);
printf("%lld\n",ans);
return 0;
}