一、开头
神犇MX:怎么样?xyz32768,你学不会后缀自动机,真是菜啊!
xyz32768:是啊,我本来就很菜啊!
神犇MX:你不服的话,再考你一道题:给定一个nn个数的数集,求一个子集,使这个子集的和最大?只需要输出这个xorxor和。n≤105n≤105并且每个数不大于260260。
xyz32768:爆搜子集,O(2n)O(2n),我好像只会这样了。
神犇MX:看来你还是太菜,连线性基都不会,怎么可能会后缀自动机呢?
xyz32768:啊啊啊啊啊啊啊啊啊啊啊啊啊啊……(省略1099999910999999个“啊”)
二、介绍
一个nn个数的正整数集合,这个集合的线性基是一个mm个正整数的集合:
线性基有两点性质:
①:如果存在S′⊂SS′⊂S且S′S′内元素的xorxor和为tt,那么就一定存在且A′A′内的元素的xorxor和为tt,反过来也一样。简单的说,通过集合内的元素xorxor出的值域与通过SS集合内的元素出的值域相同。
②:线性基还有一个重要的性质:对于任何一个ii,要么不存在,要么aiai最高位的11在第位上。
三、构造(插入一个新数)
集合SS为空时,线性基即集合的每一个元素(即a1,a2,...a1,a2,...)都不存在。
向线性基中插入新数pp的方法:
从高到低扫描的每一个二进制位,扫描到pp的第位为11时,分两种情况:
如果不存在,那么令ai=pai=p并且结束扫描。
否则令p=p xor aip=p xor ai并继续扫描。
简单解释:假设插入新数pp之前的线性基为,设A′A′满足线性基的两个性质,那么在aiai不存在并且pp最高位的在第ii位的情况下,令,得到的线性基仍然满足性质。
而在aiai存在的情况下令p=p xor aip=p xor ai是为了使pp的第位为00,以此维护性质②。这时候异或上aiai之后等于待插入的新数,而两个相等的数的异或值总是00,这时候仍然满足线性基的性质①。
代码(代码中的表示线性基,orzi=−1orzi=−1就表示orziorzi不存在):
void ins(ll p) {
int i; for (i = m; i; i--) {
if (!((p >> i - 1) & 1)) continue;
if (orz[i] == -1) return (void) (orz[i] = p);
else p ^= orz[i];
}
}
合并两个线性基,就是把一个线性基中的元素暴力插入到另一个线性基里面。
代码:
cyx mer(cyx a, cyx b) {
int i; for (i = m; i; i--)
a.ins(b.orz[i]);
return a;
}
四、查询
1、最大异或和
贪心。从高往低扫aa,对于一个,如果aiai存在,并且异或上aiai后会使结果更大,就异或上aiai。通过性质②可以得出正确性。
代码:
ll max_xor() {
int i; ll ans = 0; for (i = m; i; i--)
if ((ans ^ orz[i]) > ans) ans ^= orz[i];
return ans;
}
2、一个数pp能否被异或出
贪心,同样是利用性质②。首先记下。从高往低扫aa。
对于一个,如果aiai存在,并且resres的第ii位与的第ii位不等,则使。
如果最后res=pres=p,那么pp能被异或出。当然需要特判:
如果SS中有,或者在构建线性基的过程中,存在一个待插入的数kk使得插入之前能被异或出,那么存在一个非空子集的异或和为00。
代码(不加特判):
bool _xor(ll p) {
int i; ll res = 0;
for (i = m; i; i--) {
if (orz[i] == -1) continue;
if (((p >> i - 1) & 1) != ((res >> i - 1) & 1))
res ^= orz[i];
}
return res == p;
}
五、题目
(待补充)