\(#defing ll long long\)
线性基用处:
快速查询一个数是否可以被一堆数异或出来
快速查询一堆数可以异或出来的最大 \(/\) 最小值
快速查询一堆数可以异或出来的第 \(k\) 大值
线性基空间复杂度:
设有一个序列,其值域为 \([1,N]\),我们可以构造一个长度为 \(⌈\log_2 N⌉\) 的线性基。
其中,满足性质:序列能异或出来的数,线性基都能异或出来,反之,线性基能异或出来的数,序列都能异或出来。
证明一定存在一种构造的线性基能满足这个性质。
不妨设整个序列的最大值为 \(N\),则一定存在 \(i=⌈\log_2 N⌉\) 满足 \(2^{i-1}< N\le 2^i\),则这个序列最多最多,只能异或出 \(2^i\) 个数,而我们线性基的所有组合也是 \(2^i\),只要构造出的线性基满足每种组合不一样即可(感性理解,感性裂解)
贪心构造线性基:
令线性基为数组 \(a\),令 \(a_i\) 最高位表示 \(2^i\)。
考虑对于一个数 \(x\),从高到低每一位枚举,如果 \(x\) 在第 \(j\) 位为 \(1\)。
若 \(a_j\) 没有记录,则令 \(a_j=x\)
否则的话,令 \(x=x \oplus a_j\)
最后在特判一下,看一下 \(x\) 是否为 \(0\)。
若是,则记录一下,表示这些数可以异或出来 \(0\)。
void ins(ll x)
{
for(ll i=N;i>=0;i--)
if(x&(1ll<<i))
if(!a[i]){a[i]=x;return ;}
else x^=a[i];
flag=true;
}
判断一个数能否被序列异或出来:
跟构造差不多,只不过变成了直接\(return\) \(true/false;\)罢了
bool check(ll x)
{
for(ll i=N;i>=0;i--)
if(x&(1ll<<i))
if(!a[i])return false;
else x^=a[i];
return true;
}
查询异或最小值:
首先看一下能否表示 \(0\)。
若不能,因为线性基的位数都是单调递减的,任意两个数异或出的数必然会大于小一点的那个数,所以直接输出线性基的最小值即可
ll qmin()
{
if(flag)return 0;
for(ll i=0;i<=N;i++)
if(a[i])return a[i];
}
查询异或最大值:
从高到低枚举线性基 \(a_i\),在异或和不异或之间取 \(max\) 即可。
ll qmax()
{
ll re=0;
for(ll i=N;i>=0;i--)
re=max(re,re^a[i]);
return re;
}
查询异或\(k\)小值:
个人认为这个比较难懂,
首先,先特判\(0\)
然后要构造出一个 \(b\) 数组,\(b_i\) 表示最高位为 \(2^i\) 且该位为 \(1\) 时能表示出的最小异或值
很显然,有些 \(b_i\) 是不存在,所以我们可以构造一个 \(tmp\) 数组,\(tmp_j\) 表示从小到大第 \(j\) 个可以表示的 \(b_i\)。(感性理解)
设 \(tmp\) 长度为 \(cnt\),可以发现我们最多构造出 \(2^{cnt}-1\)个数,所以如果有解必然是 \(2^{cnt} > k\)。
不能发现