线性基

对于一个序列,我们要求它异或的最大/最小值,这是线性基应用的经典情况。
一个序列 \(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\n",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\n",(ksm(2,siz))%mod);
	return 0;
}

posted @ 2021-10-21 15:19  lzylzy/kk  阅读(63)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end