力扣周赛困难-3681. 子序列最大 XOR 值 (线性基)
3681. 子序列最大 XOR 值 (线性基)
leetcode-子序列最大 XOR 值 (线性基)
qwen-image
简单的推导之后, 我们就可以把这个问题转化为一个经典的 线性基问题:
在题目中的条件下, 可以选中两个子序列, 然后一起做XOR, 那么我们可以理解为, 对于每个数字, 可以在 XOR 中出现 0/1/2 次数, 但是因为 a XOR a = 0
, 所以说出现2次等价于出现0次, 于是对于每个数字, 可以在 XOR 中出现 0/1 次, 这就成为了经典问题:
求一个数组的子序列的最大异或值
线性基
参考资料:
- 灵茶山艾府-线性基【力扣周赛 460】
- oiwiki-线性基
首先, 我们考虑这样一个问题, 对于二维空间中的向量, 我们如何进行"表出".
如果用过去所学的正交分解法, 比如向量 v=⃗(5,6)v \vec = (5, 6)v=(5,6) 实质上就表出了 v⃗=5⋅i⃗+6⋅j⃗\vec v = 5 \cdot \vec i + 6 \cdot \vec jv=5⋅i+6⋅j, 其中的 i⃗,j⃗\vec i, \vec ji,j 就是两个线性基;
可以证明的是, 二维空间中的所有向量都可以用这样的两个线性无关的基向量来进行表出, 这里的表出指的是: 对任意的 t, 总存在 λ1,λ2∈R\lambda_1, \lambda_2 \in Rλ1,λ2∈R 使得 t⃗=λ1⋅i⃗+λ2⋅j⃗\vec t = \lambda_1 \cdot \vec i + \lambda_2 \cdot \vec jt=λ1⋅i+λ2⋅j;
同样可以证明, 对于 n 维空间, 所有向量都可以用符合条件的 n 个线性无关的基向量来进行表出
上面我们所说的情况是 加法 的情况, 那么对于 按位异或 是不是有类似的性质呢?
不严谨的证明, 可以做一波高斯消元, 然后得到的就是 a1=(1,0,0)...a_1 = (1,0,0)...a1=(1,0,0)..., 然后高斯消元的过程中, 即使一个东西出现了多次, 也可以直接做 %2 就可以得到, 确保每一个数字都是可以被"表出"出来的, 这里的表出就变成了: 对于任意的 n 位(也是维度) 向量 t 来说, 总是存在 λ1,...,λn∈{0,1}\lambda_1, ..., \lambda_n \in \{0, 1\}λ1,...,λn∈{0,1}, 使得 t⃗=∑i=1i=nλiei⃗\vec t = \sum_{i=1}^{i=n} \lambda_i \vec {e_i}t=∑i=1i=nλiei
接下来, 我们关注的就是线性基如何去构造:
由上面可知, 我们 32位 二进制数对应的是 32 维线性空间, 所以最多存在 32 个线性基, 于是我们可以用 int s[32]
来维护32个线性基.
下面的问题是, 如何遍历一些数字, 快速判断出它是否能被目前维护的线性基 表出; 一个巧妙的办法是, 确保每一个基的最高位1的位置都不同, 当尝试插入一个新的数字时, 我们将该数字从高位数到低位数进行考察, 比如该数字的第 i 位是1, 此时有两种情况:
- 最高位是第 i 位的线性基还没有找到, 这时我们一定可以将该数字添加到线性基中;
- 最高位是第 i 位的线性基找到了, 这个时候我们虽然不能立刻得知插入数字是否可以被表出, 但是我们可以通过
new_num = new_num ^ s[i]
将new_num
退化, 最高位数就会降低, 如果最后退化成了0, 那么就相当于被成功表出, 否则一定会走1添加进去
代码如下:
int s[32] = {};
int size = nums.size();for (int i = 0; i < size; i++) {// for (int bit = 31; bit >= 0; bit--) {// 这里表示位数if (nums[i] & 1 << bit) {// 最高位确实是 bitif (s[bit] == 0) {s[bit] = nums[i];break;} else {// 否则, 做一下异或, 然后接着向下看nums[i] ^= s[bit];}}}
}
接下来, 我们关注的是, 这样去构造的线性基的特点, 并且这个特点直接导致我们后面可以使用贪心算法
因为这个线性基, 可以确保上面的最高位各不相同, 那么我们就自然可以使用贪心, 尽可能取用高位1;
int ans = 0;
for (int i = 31; i >= 0; i--) {if ((ans ^ s[i]) > ans) {ans ^= s[i];}
}
ps: 位运算的优先级问题需要注意, 一般来说位运算的优先级是比较低下的, 所以需要加上括号, 比如说:
ans ^ s[i] > ans
必须写成是(ans ^ s[i]) > ans
ps: 线性基合并 / 线性基求交问题
- 合并, A 向 B 插入即可
- 求交, A 尝试向 B 插入:
- 如果 αi\alpha_iαi 最后退化成0, 说明可以被 B 表出, 那么加入交集;
- 如果没有退化成0, 说明不可以被 B 表出, 那么加入 B 中(用来表出后续的 αi+1\alpha_{i+1}αi+1)