线性基
对于一个序列,我们要求它异或的最大/最小值,这是线性基应用的经典情况。
一个序列 \(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。这样就很巧妙的把异或问题变成了贪心。
求能异或出来的最大值。
#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;
}