线性基

对于一个序列,我们要求它异或的最大/最小值,这是线性基应用的经典情况。
一个序列 (a) 的线性基,指一个等效数组 (p) ,这两个数组元素异或的值域相同
设一个序列 (a) 的线性基为 (p)(p_i) 表示出现 (1) 的最高位在第 (i) 位上的数字。
显然,这个数组的大小仅有 (logn) 所以就能处理更多问题。

  • 构造方法
    由于 (p) 是一个等效数组,也就是说,p的元素没必要都在 (a) 中出现。只需要保证可以通过异或操作拼出 (a) 中的元素就可以
    于是对于加入一个数 (x) 的操作:
    从高到低检查x在二进制下的每一位,若x当前位置为1 则检查 (p_i)
    (p_i)为1 则将x异或(p_i),继续寻找
    (p_i)为0 则(p_i=x),插入成功
    (x) 值变成0,则证明 (x) 已经可以用线性基中的数字表示,插入失败。
    当查询最大值时,我们只需要对于每一位进行贪心,如果异或上之后会使答案变大就进行异或操作。
    至于最小值,一般来说就是线性基里的最小元素,因为插入这个元素的时候我们总是尽量让它的高位为 (0) 才来插入这一位。但是为什么是“一般”呢?因为有可能会有出现 (0) ,得要在插入的时候记下个标记来特判才行。
  • 为什么是这样?
    对于每一位,我们都努力给它匹配一个唯一的数字使得这一位能异或出来1。这样就很巧妙的把异或问题变成了贪心。

例题 P3812 【模板】线性基

求能异或出来的最大值。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<map>
#include<cstring>
#include<stack>
#include<queue>
#include<vector>
#include<bitset>
#include<set>
#define ll long long
using namespace std;
const int inf = 0x7fffffff;
int n;
ll p[60];
bool Add(ll x)
{
	for(int i=60;i>=0;i--)
	{
		if(x&(1ll<<i))
		{
			if(!p[i])
			{
				p[i]=x;
				return 1;
			}else x^=p[i];
		}
		if(x==0)return 0;
	}
}
ll Query()
{
	ll ans=0;
	for(int i=60;i>=0;i--)
	{
		if((ans^p[i])>ans)ans^=p[i];
	}
	return ans;
}
signed main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		ll x;
		scanf("%lld",&x);
		Add(x);
	}
	printf("%lld
",Query());
	return 0;
}

例题 P3857 [TJOI2008]彩灯
每个开关控制了一些特定的灯
求一共能搞出多少种不同形态的灯。

发现开关一个开关其实就相当于异或操作
于是问题变成了给你一些数字 问互相异或能找出多少个不同的数字。
使用线性基 因为线性基内的数字在异或下的值域和原数组相同 所以线性基每个数都有选或不选两种方案 答案数量即为 (2^{size})

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<map>
#include<cstring>
#include<stack>
#include<queue>
#include<vector>
#include<bitset>
#include<set>
#define ll long long
using namespace std;
const int inf = 0x7fffffff;
int n,m;
#define mod 2008
ll ksm(ll a,ll b)
{
	ll base=a,res=1;
	while(b)
	{
		if(b&1)res=res*base%mod;
		base=base*base%mod;
		b>>=1;
	}
	return res;
}
ll p[60],siz=0;
bool Add(ll x)
{
	for(int i=60;i>=0;i--)
	{
		if((x>>i)&1)
		{
			if(!p[i])
			{
				p[i]=x;
				siz++;
				return 1;
			}else x^=p[i];
		}
		if(x==0)return 0;
	}
	return 0;
}
char ch[88];
signed main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%s",ch+1);
		ll now=0;
		for(int j=1;j<=n;j++)
		{
			if(ch[j]=='O')now++;
			now<<=1;
		}
//		cout<<now/2<<endl;
		Add(now>>1);
	}
//	cout<<siz<<endl;
	printf("%lld
",(ksm(2,siz))%mod);
	return 0;
}

原文地址:https://www.cnblogs.com/lzy-blog/p/15433580.html