本文在学习了博客线性基学习笔记,总结自己的一些理解
对于一组数,通过构造一组线性无关的基底来求解异或最值、异或第k小值以及某值是否可得到的问题。
维护一组线性基:对于每一个给定的数,对其二进制表示,从高位到低位扫描,当发现第x位是1时,若当前最高位为x的基底(即bs[x])为空,则bs[x]置为该数,否则将该数异或上bs[x],继续往下扫描。
ll bs[70];
void insert(ll x)
{
for (int i=62;i>=0;--i)
{
if (x&(1ll<<i))
{
if (!bs[i])
{
bs[i]=x;
return;
}
x^=bs[i];
}
}
}
对于给出的数,将其二进制表示看成一个向量,对这一组向量,按上述方法,构造出一组线性无关基底,满足下列性质:
对于性质一,容易看出按照上述方法可以构造出来。
对于性质二,这是后面用来求解异或最值等问题的正确性的保证。证明这个性质的大概思想:令c=a xor b,则a和c可以得到b,则c可以代替b,加入本该是a、b的组中。
对于性质三,参考开头提到博客的证明方法:
假设x的组合方法不唯一, 也就是说 x=a1 xor a2 ? ap = b1 xor b2 ? bq
那么x xor x = 0 = a1 xor a2 ? ap xor b1 xor b2 ? bq
也就是说 可以用这个向量组里的向量组合出0向量, 与线性无关矛盾。 故组合方法唯一。
求最大值:从最高位开始,若异或上该基底可以使当前值变大,则异或。做到最低位结束,得到即为答案
ll max_qry(ll init=0) // init为初始值,若没有,则置为0;
{
for (int i=62;i>=0;--i)
{
if ((init^bs[i])<init)
init^=bs[i];
}
return init;
}
求最小值:从低位往上找到的第一个非零基底。这里特别提一下,若有初始值,则应该仿照上述求最大值的方法,从高位开始贪心异或,能使其变小就异或上。
ll min_qry(ll init=0)
{
if (init==0)
{
for (int i=0;i<=62;++i)
if (bs[i])
return bs[i];
}
else
{
for (int i=62;i>=0;--i)
if ((init^bs[i])<init)
init^=bs[i];
return init;
}
}
从高位向低位扫这个数的二进制表示,碰到值为1的位,则异或上最高位为这个位的基底。若最后该数变为0,则表示该数可以被这组基的子集构造出来。结合性质三感受一下。
bool check(ll x)
{
for (int i=62;i>=0;--i)
if (x&(1ll<<i))
x^=bs[i];
return x==0;
}
在原有线性基的基础上,重新构造一组基底。这组基底相比于原来的,每个基底变为通过异或得到的包含原来最高位的最小值。再对k二进制表示,异或上对应位的新基底即可。这里特别提一下,0的情况。如果构造基底的个数少于题目给定的数的个数,说明0使可以被异或出来的。那么对于k,要使k=k-1。
方法如下
vector<ll> tmp;
ll kth_query(int k)
{
for(int i=0;i<=62;++i)
{
if (!bs[i])
continue;
for(int j=i-1;j>=0;--j)
if(bs[i]&(1ll<<j))
bs[i]^=bs[j];
tmp.pb(bs[i]);
}
if (k>=(1<<sz(tmp)))
return -1;
ll res=0;
for(int i=0;i<sz(tmp);++i)
if(k&(1ll<<i))
res^=tmp[i];
return res;
}
原文:https://www.cnblogs.com/orangee/p/10159717.html