因为 \((a_i, b_i)\) 虽然是对的形式,但是异或是同时的。
于是可以考虑把两元先缩为一元,只需要让 \(a_i, b_i\) 互不干扰即可。
那就可以把 \((a_i, b_i)\) 当作数 \(c_i = a_i\times 2^{31} + b_i\)。
那么最后 \(c_i\) 异或出来的结果 \(c\),就可以还原出 \(a = \lfloor\frac{c}{2^{31}}\rfloor, b = c\bmod 2^{31}\)。
因为 \(n\) 比较大,肯定还是想 \(n\) 尽量小。
结合操作是异或,可以考虑对 \(c\) 建出线性基,那么这样新 \(n\) 就被缩减到了 \(\mathcal{O}(\log V)\)。
接下来考虑来求解,因为对于 \(\min(a, b)\) 是一个双向限制,一个想法是去掉一个方向上的限制。
那么就可以考虑去二分 \(k = \min(a, b)\),那么只保留下 \(b\ge k\) 这个限制(为什么是选择 \(b\) 而不是 \(a\),将会在后面说到),对于 \(a\) 直接考虑最大化并在最后判断 \(a, k\) 的大小关系就行了。
那么接下来考虑如何满足 \(b\ge k\) 的同时最大化 \(a\)。
首先如果最大化 \(b\) 都无法使得 \(b\ge k\),那肯定不合法。
注意到建完线性基其实还有一个很好的性质是,对于每个最高位,至多有一个元素的最高位为该位。
再结合上对于线性基求异或最大值时常用的一个贪心的方法是直接从高位到低位,\(\operatorname{ans}\leftarrow \max\{\operatorname{ans}, \operatorname{ans}\oplus a_i\}\),这实际上也是上述的性质保证了正确性。
那么这个时候能发现,因为以 \(c_i = a_i\times 2^{31} + b_i\) 作为元素,最后的线性基对于 \(a_i\) 的每个位最多只有一个元素最高位为此位,否则就是 \(a_i = 0\)。
那么能发现此时对于 \(a\) 来说依然能用上述所提到的贪心 \(a\leftarrow \operatorname{max}\{a, a\oplus a_i\}\),这也是选取最大化 \(a\) 而不是 \(b\) 的原因,因为 \(b_i\) 不满足此性质。
那么就可以对于 \(\mathcal{O}(\log V)\) 组 \((a_i, b_i)\),按照 \(a_i\) 从大到小排序,然后依次决定是否要让 \(a\leftarrow a\oplus a_i\)。
那么就可以维护当前的 \(a, b\),接下来考虑 \((a_i, b_i)\) 这个对是否要异或上,就可以考虑分讨:
- 如果不异或保留 \(b\),在 \([i + 1, n]\) 无法使得 \(b\) 能被异或出 \(\ge k\)。
那么就必须要让 \(b\leftarrow b\oplus b_i\)。 - 否则此时已经可以选择不异或保留 \(b\) 了。
那么如果 \(a\oplus a_i > a\) 且 \(b\oplus b_i\) 能在 \([i + 1, n]\) 中被异或使得 \(\ge k\),就会贪心的选择异或,即 \(a\leftarrow a\oplus a_i, b\leftarrow b\oplus b_i\)。
否则就会选择保留,即 \(a, b\) 不变。
至于如何判断 \([i + 1, n]\) 能否通过异或使得 \(x\) 异或后 \(\ge k\)。
依然可以考虑最大化异或值,然后判断是否 \(\ge k\)。
那么就可以建一个后缀的线性基,每次借用 \([i + 1, n]\) 的信息插入 \(b_i\),就得到了 \([i, n]\) 的信息。
时间复杂度 \(\mathcal{O}(n\log V + \log^3 V)\)。
#include<bits/stdc++.h>
using ll = long long;
constexpr int maxn = 2e5 + 10, maxV = 65;
ll a[maxn], b[maxn], c[maxn];
ll f[maxV][maxV];
inline void solve() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
for (int i = 1; i <= n; i++) scanf("%lld", &b[i]);
for (int i = 1; i <= n; i++) c[i] = (a[i] << 31) | b[i];
for (int w = 61, p = 1; ~ w; w--) {
for (int i = p; i <= n; i++) {
if (c[i] >> w & 1ll) {
std::swap(c[i], c[p]);
}
}
if (~ c[p] >> w & 1ll) continue;
for (int i = p + 1; i <= n; i++) {
if (c[i] >> w & 1ll) {
c[i] ^= c[p];
}
}
p++;
}
c[n + 1] = 0;
for (n = 1; c[n]; n++);
n--;
assert(n <= 62);
for (int i = 1; i <= n; i++) {
a[i] = c[i] >> 31, b[i] = c[i] ^ (a[i] << 31);
}
memset(f[n + 1], 0, sizeof(f[n + 1]));
for (int i = n; i; i--) {
memcpy(f[i], f[i + 1], sizeof(f[i]));
ll x = b[i];
for (int w = 30; ~ w; w--) {
if (~ x >> w & 1ll) continue;
if (! f[i][w]) f[i][w] = x;
x ^= f[i][w];
}
}
auto query = [&](ll *F, ll x) {
for (int w = 30; ~ w; w--) {
x = std::max(x, x ^ F[w]);
}
return x;
};
auto check = [&](ll val) {
if (query(f[1], 0ll) < val) return false;
ll x = 0, y = 0;
for (int i = 1; i <= n; i++) {
if (query(f[i + 1], y) < val) {
x ^= a[i], y ^= b[i];
} else if (query(f[i + 1], y ^ b[i]) >= val && (x ^ a[i]) > x) {
x ^= a[i], y ^= b[i];
}
}
assert(y >= val);
return x >= val;
};
ll l = 0, r = INT_MAX, ans = 0;
while (l <= r) {
ll mid = l + r >> 1;
if (check(mid)) ans = mid, l = mid + 1;
else r = mid - 1;
}
printf("%lld\n", ans);
}
int main() {
for (int T, _ = scanf("%d", &T); T--; ) {
solve();
}
return 0;
}
标签:ge,leftarrow,int,Luogu,黄之瓜,Solution,异或,oplus,ll
From: https://www.cnblogs.com/rizynvu/p/18642417