其实这是一道线性基的板子题。
那么什么叫线性基。
看一下百度的定义:线性基是出现在信息学竞赛中的一个名词,常见于神犇博客题解中。(恩,我是神犇了,233333)
简单来讲,线性基是一种擅长处理异或问题的数据结构.
它大体有以下几个操作:
1.插入
inline void insert(ll x)
{
for(int i=MAXN;i>=0;--i)
if(x&(1ll<<i))
if(!a[i]){a[i]=x;return;}
else x^=a[i];
flag=true;
return ;
}
令插入的数为x,考虑x的二进制最高位i,
若线性基的第i位为0,则直接在该位插入x,退出;
若线性基的第i位已经有值,就把x和其异或。
很容易想到,这样得出的线性基a[i]是最高位为i(即1的最高位是i)的异或和。
这样插入,可以得到线性基的异或和的值域与原数列的异或和值域相同。
2.求最大值
inline ll qmax()
{
ll res=0;
for(int i=MAXN;i>=0;--i)
res=max(res,res^a[i]);
return res;
}
类似贪心的做法,如果x的当前位是0,那么异或一定会更优,否则当前位如果为1,则一定会更不优。
3.求最小值
inline ll qmin()
{
if(flag) return 0;
for(int i=0;i<=MAXN;++i)
if(a[i]) return a[i];
}
没什么可说的。。。。
4.查询x是否在值域中
inline bool check(ll x)
{
for(int i=MAXN;i>=0;--i)
if(x&(1ll<<i))
if(!a[i]) return false;
else x^=a[i];
return true;
}
同上。。。
5.查询第k小的值
inline ll query(ll k)
{
ll res=0; int cnt=0;
k-=flag; //减去为0的情况
if(!k) return 0;
for(int i=0;i<=MAXN;++i)
{
for(int j=i-1;j>=0;--j)
if(a[i]&(1ll<<j)) a[i]^=a[j];
if(a[i]) tmp[cnt++]=a[i];
}
if(k>=(1ll<<cnt)) return -1;
for(int i=0;i<cnt;++i)
if(k&(1ll<<i)) res^=tmp[i];
return res;
}
我们考虑进一步简化线性基。显然,一个线性基肯定可以表示为若干个形如2^i的数。
从高到低处理线性基每一位,对于每一位向后扫,如果当前数第i位为0,且线性基第i位不为0,则将当前数异或上a[i] 这一操作可以在O(n^2)的时间内解决。
经过这一步操作后,设线性基内共有cnt个数,则它们共可以表示出2^cnt个数。
随后,我们考虑将k二进制拆分,用与快速幂类似的方法就可以求出第k小值。
%%%
orz
tql
笑死了,四个月之前碰的线性基没看懂,现在终于会了。之前一直不知道重构线性基是干嘛的,今天终于发现原来是原来的 $a_i$ 的 $0$~$i-1$位不一定是 $0$,而重构线性基就是将 $a_i$ 的$0$~$i-1$位都变成 $0$,只有第 $i$ 位是 $1$,便于求第 $k$ 小的值
$ good $
支持!
可以
赞orz
写的不好吗?怎么没人支持,是不是因为没有给完整的ac代码??
是因为没多少人学到这里