线性基

简介

线性基常用来处理异或问题。对于一个值域 \([1,n]\) ,就可以用一个长度为 \(\lceil \log_2 n \rceil\) 的数组来描述一个线性基。
特别地,线性基中第 \(i\) 位上的数在二进制下也有 \(i\) 位。

性质

线性基满足:对于它所表示的数的集合 \(S\)\(S\) 中任意多个数异或的结果均可以用线性基中的数异或的结果表示。

插入

令插入的数为 \(x\) ,设 \(x\) 在二进制下有 \(i\) 位。
若线性基的第 \(i\) 位为 \(0\) ,则在第 \(i\) 位上插入 \(x\) ,结束。
反之,则将 \(x\) 异或上当前线性基中第 \(i\) 位的值 \(a_i\) ,并继续插入。
重复以上操作,直到 \(x\) 变为 \(0\)
如果结束时 \(x=0\) ,因为 \(x \oplus x=0\) ,所以在原来的线性基中就可以表示 \(x\) 了。
反之,则表示此时往线性基中加入了一个新元素,此时也能表示 \(x\) 了。

判断

用上面插入的方法判断,若最后 \(x=0\) ,则能表示,反之,不能表示。

查询异或最小值

根据线性基的定义,线性基中的所有数在二进制下的位数都不同。
所以,考虑线性基中最小的数,它异或上其它数一定比它原先的值大。
所以,线性基中(即原序列中)的数能通过异或产生的最小值即为线性基中最小的数。

查询异或最大值

考虑贪心,从高位到低位遍历线性基,若这一位上的值 \(a_i\) 在二进制下的第 \(i\) 位为 \(1\) 并且此时答案在二进制下的第 \(i\) 位为 \(0\) ,那么就将答案异或上 \(a_i\) ,因为这样答案一定不会变劣。
因此,遍历完线性基之后得到的答案一定就是线性基中(即原序列中)的数能通过异或产生的最大值。

例题

1
最板子的板子。

#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
const int N=60;
int n,a[N],b[N];
int max_(int x,int y)
{
	return x>y?x:y;
}
void ins(int x)
{
	for(int i=N-1;i>=0;i--)
		if(x&((int)1<<i))
		{
			if(!b[i])
			{
				b[i]=x;
				return;
			}
			x^=b[i];
		}
}
int query()
{
	int sum=0;
	for(int i=N-1;i>=0;i--)
		sum=max_(sum,sum^b[i]);
	return sum;
}
signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]),ins(a[i]);
	printf("%lld",query());
	return 0;
}

2
先进行一步转化,把开关的控制转化为异或上一个数。
很显然,这样的转化是成立的。
于是,把所有开关代表的数丢进线性基,最终,求出线性基中元素的数量 \(x\)
可以得到答案为 \(2^x\)
(每个元素都有选与不选两种情况,同时,线性基中的数不可能异或得到相同的数。)

#include<iostream>
#include<cstring>
#include<cstdio>
#define int long long
using namespace std;
const int N=60;
int n,m,b[N];
char a[N];
void ins(int x)
{
	for(int i=N-1;i>=0;i--)
		if(x&((int)1<<i))
		{
			if(!b[i])
			{
				b[i]=x;
				return;
			}
			x^=b[i];
		}
}
signed main()
{
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%s",a);
		int sum=0;
		for(int j=0;j<n;j++)
			sum+=((int)1<<j)*(a[j]=='O');
		//cout<<sum<<endl;
		ins(sum);
	}
	int ans=0;
	for(int i=0;i<N;i++)
		if(b[i]) ans++;
	//cout<<ans<<endl;
	printf("%lld",((int)1<<ans)%(int)2008);
	return 0;
}

3
\(XOR\) 最大值有关,考虑用线性基求解。
考虑题目中一句重要的话:
路径可以重复经过某些点或边,当一条边在路径中出现了多次时,其权值在计算 XOR 和时也要被计算相应多的次数。
一条边走多遍,一定不会得到更大的贡献,但是,可以让你不花费更大的代价走到一个环里并走回来。
题目保证图为连通图,所以每个环都一定能走到。
注:下文中链和环的价值即为链和环的 \(XOR\) 和。
把所有环的价值都丢进线性基里,选一条链的价值作为初值,求 \(XOR\) 最大值。
如何选择作为初值的链?
假设 \(1\) ~ \(n\) 的路径有 \(A\)\(B\) 两条。
\(A\)\(B\) 一定会共同组成一个环。
假设我们先选择了 \(A\) 作为初值,如果 \(B\) 更优,因为所有环的价值都已经丢进了线性基里,所以求最大值时一定会发现异或上这个环的价值会使答案更优,所以最终也能得到 \(B\) 的价值。
因为所有 \(1\) ~ \(n\) 的路径一定会两两组成若干个环,所以无论选择哪条链的价值作为初值,最终都可以得到答案。
综上,可以随意选择一条 \(1\) ~ \(n\) 的路径的价值作为初值。

#include<iostream>
#include<cstdio>
#include<vector>
#define int long long
using namespace std;
const int N=5e4+10;
const int M=65;
struct node
{
	int to,val;
};
vector <node> e[N];
int n,m,cnt[N],vis[N];
int b[M];
int max_(int x,int y)
{
	return x>y?x:y;
}
void ins(int x)
{
	for(int i=M-2;i>=0;i--)
		if(x&(int)((int)1<<i))
		{
			if(b[i]) x^=b[i];
			else
			{
				b[i]=x;
				return;
			}
		}
}
int query(int x)
{
	int sum=x;
	for(int i=M-2;i>=0;i--)
		sum=max_(sum,sum^b[i]);
	return sum;
}
void dfs(int x,int sum)
{
	vis[x]=1,cnt[x]=sum;
	for(int i=0;i<e[x].size();i++)
	{
		if(!vis[e[x][i].to])
			dfs(e[x][i].to,sum^e[x][i].val);
		else ins(sum^e[x][i].val^cnt[e[x][i].to]);
	}
}//判环,并且将环的价值扔进线性基里
signed main()
{
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int x,y,z;
		scanf("%lld%lld%lld",&x,&y,&z);
		e[x].push_back((node){y,z});
		e[y].push_back((node){x,z});
	}
	dfs(1,0);
	printf("%lld",query(cnt[n]));
	return 0;
}

4
考虑普通 NIM 游戏,若所有元素的异或和不为0,则先手必胜。
因为要最小化不能加入线性基的数的和,所以将元素从大到小排列,然后能取则取。
判断能不能取的部分用线性基实现。

#include<algorithm>
#include<iostream>
#include<cstdio>
using namespace std;
const int N=110;
const int M=60;
int n,a[N],b[M];
long long ans;
bool ins(int x)
{
	for(int i=M-1;i>=0;i--)
		if(x&(1<<i))
		{
			if(b[i]) x^=b[i];
			else
			{
				b[i]=x;
				return true;
			}
		}
	return false;
}
bool cmp(int x,int y)
{
	return x>y;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++)
		if(!ins(a[i])) ans+=(long long)a[i];
	printf("%lld",ans);
	return 0;
}

5
用和LCA一样的方法倍增维护路径上的线性基。
合并线性基时暴力将一个线性基内的元素插入另一个线性基里。
复杂度约为 \(O(q \log n \log G_i)\) ,还有一定的常数,需要卡常。
卡常技巧:
1.插入的数为0时直接不插。
2.若当前线性基已经满了直接不插。

#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
const int N=2e4+10;
const int M=64;
struct node
{
	ll A[M];
	bool flag;
}F[N][20];
struct edge
{
	int nxt,to;
}e[N*2];
int cnt,head[N];
int n,q,d[N];
ll a[N];
int f[N][20],lg[N];
ll MAX(ll x,ll y)
{
	return x>y?x:y;
}
void add(int x,int y)
{
	e[++cnt].to=y;
	e[cnt].nxt=head[x];
	head[x]=cnt;
}
node operator + (node x,ll y)
{
	if(!y) return x;
	if(x.flag) return x;
	for(int i=M-1;i>=0;i--)
		if(y&(1ll<<i))
		{
			if(x.A[i]) y^=x.A[i];
			else
			{
				x.A[i]=y;
				break;
			}
		}
	return x;
}
node operator + (node x,node y)
{
	bool f=false;
	for(int i=M-1;i>=0;i--) x=x+y.A[i];
	for(int i=0;i<M;i++)
		if(!x.A[i])
		{
			f=true;
			break;
		}
	if(!f) x.flag=true;
	return x;
}
ll query(node x)
{
	ll sum=0;
	for(int i=M-1;i>=0;i--)
		sum=MAX(sum,sum^x.A[i]);
	return sum;
}
void dfs(int x,int y)
{
	f[x][0]=y,F[x][0]=F[x][0]+a[x];
	d[x]=d[f[x][0]]+1;
	for(int i=1;i<=lg[d[x]]+1;i++)
		f[x][i]=f[f[x][i-1]][i-1];
	for(int i=1;i<=lg[d[x]]+1;i++)
		F[x][i]=F[x][i-1]+F[f[x][i-1]][i-1];
	for(int i=head[x];i;i=e[i].nxt)
		if(e[i].to!=y) dfs(e[i].to,x);
}
ll LCA(int x,int y)
{
	node ans;
	for(int i=0;i<M;i++) ans.A[i]=0;
	ans.flag=false;
	if(d[x]<d[y]) swap(x,y);
	while(d[x]>d[y])
	{
		ans=ans+F[x][lg[d[x]-d[y]]];
		x=f[x][lg[d[x]-d[y]]];
	}
	if(x==y)
	{
		ans=ans+F[x][0];
		return query(ans);
	}
	for(int i=lg[d[x]];i>=0;i--)
		if(f[x][i]!=f[y][i])
		{
			ans=ans+F[x][i],ans=ans+F[y][i];
			x=f[x][i],y=f[y][i];
		}
	ans=ans+F[x][0],ans=ans+F[y][0],ans=ans+F[x][1];
	return query(ans);
}
int read()
{
	int sum=0;
	char x=getchar();
	while(!(x>='0'&&x<='9')) x=getchar();
	while(x>='0'&&x<='9') sum=sum*10+x-'0',x=getchar();
	return sum;
}
int main()
{
	for(int i=2;i<N;i++) lg[i]=lg[i/2]+1;
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=1;i<n;i++)
	{
		int x=read(),y=read();
		add(x,y),add(y,x);
	}
	dfs(1,0);
	while(q--)
	{
		int x=read(),y=read();
		printf("%lld\n",LCA(x,y));
	}
	return 0;
}
原文地址:https://www.cnblogs.com/zhs1/p/13708371.html