CF2115D Gellyfish and Forget-Me-Not
题目链接
Part1:一些转化
为了使操作形式更加好看,我们在所有操作前,将答案异或上 \(\oplus_{i=1}^n b_i\)。然后令 \(c_i=a_i\oplus b_i\)。这样每次只需要决定一个 \(c_i\) 选不选就行了。
显然转化后与原问题等价。
- 不选 \(c_i\),对应着选择 \(b_i\oplus 0=b_i\)
- 选择 \(c_i\),对应着选择 \(b_i\oplus c_i=a_i\)
Part2:做法推导
运算与进制有关的,且答案求最值的,优先考虑从高位往低位贪心。
先考虑 \(c_i\in [0,1]\) 怎么做。显然操作最后一个 \(c_i=1\) 的人能决定答案。带入到原问题中,二进制最高位是 \(0/1\),取决于最后一个最高位为 \(1\) 的数将要被谁操作。
然而,前面那些最高位为 \(1\) 的 \(c_i\),能带来的影响也并非没有变化。我们用 \(y\) 代表最后一个,\(x\) 代表前面的 \(c_i\)。
为了方便考虑,假设前面的 \(x\) 唯一,且操作 \(y\) 的人希望最大化。注意到此时有 \(x\) 选 \(y\) 不选,或 \(x\) 不选 \(y\) 选。两种情况相差一个 \(x\oplus y\) 的贡献。
换而言之,\(x\) 在此时的作用等价于一个 \(c_i=x\oplus y\) 的数。故而考虑将所有前面的 \(x\) 异或上 \(y\)。
更低位可以效仿当前的做法。
最后将会存在若干能决定一个位是 \(0/1\) 的数,按决定的位从高往低依次模拟两个人怎么选就行了。
- 注:这个过程其实很类似线性基。
Part3:代码实现
点击查看代码
#include <bits/stdc++.h>
#define FL(i, a, b) for (int i = (a); i <= (b); ++i)
#define FR(i, a, b) for (int i = (a); i >= (b); --i)
using namespace std;
typedef long long ll;
const int N = 1e5 + 10, LG = 60;
ll ans, a[N], b[N];
pair<ll, char> t[LG];
void Insert(ll x, char c) {
FR(i, LG - 1, 0) {
auto &[y, d] = t[i];
if (x >> i & 1) {
if (!y) {
y = x, d = c;
return;
}
x ^= y;
}
}
}
char c[N];
void Solve() {
int n = ans = 0;
scanf("%d", &n);
FL(i, 1, n) scanf("%lld", &a[i]);
FL(i, 1, n) scanf("%lld", &b[i]);
scanf("%s", c + 1);
FR(i, n, 1) {
Insert(a[i] ^ b[i], c[i]);
ans ^= b[i];
}
FR(i, LG - 1, 0) {
auto &[y, d] = t[i];
if (d == '0') {
ans = min(ans, ans ^ y);
} else {
ans = max(ans, ans ^ y);
}
y = d = 0;
}
printf("%lld\n", ans);
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
Solve();
}
return 0;
}