全世界都在研究怎么赢,只有我在大输特输。
如此状态,如何 NOIP?
题面简述
link
给出 \(N\) 个长为 \(C\) 的字符串,每个字符串由 G 或 H 组成,对于每一个字符串,求出它和其它字符串同一位置字符不同的最大值。
解题思路
注意到 \(C\) 仅有 18,我们可以把字符转换成二进制数字,此时问题转化为:求 \(\max_{j = 1}^{n} \text{popcount}(a_i \oplus a_j)\)。其中 \(\text{popcount}\) 为数字在二进制下的位数。
考虑如何求解。我们知道 \(a_i\) 按位取反之后有当前答案最大,为了方便我们设 \(a_i\) 取反后的数为 \(b_i\),如果知道在剩下的数字当中和 \(b_i\) 不同的位数最小的数字为 \(a_j\),此时有 \(C - \text{popcount}(a_j \oplus b_i) = \max_{k = 1}^{n} \text{popcount}(a_i \oplus a_k)\)。\(\text{popcount}(a_j \oplus b_i)\) 翻译成人话就是 \(a_j\) 与 \(b_i\) 在二进制下同一位置数字不同的数量。
这其实很好理解,因为每一位只有两种情况,我们又知道 \(a_i\) 与 \(b_i\) 的每一位都不同,此时我们知道有一个 \(a_j\) 和 \(b_i\) 在二进制下共有 \(x\) 位不相同,那么就有 \(a_i\) 和 \(a_j\) 有 \(x\) 位相同,即 \(a_i\) 和 \(a_j\) 有 \(C - x\) 位不同。
现在我们要知道 \(\text{popcount}(a_j \oplus b_i)\) 该怎么做?首先枚举每个 \(a_j\) 肯定不行,但是由于 \(C\) 比较小,也许可以一位一位的考虑。
对于每一个 \(a_i\),我们改变它二进制下某一位上的数字,会得到一个新的数字,再用新的数字重复这个操作,在第 \(x\) 次操作后 \(a_i\) 就会变成 \(b_j\),这个操作数 \(x\) 即为 \(\text{popcount}(a_i \oplus b_j)\),也是 \(b_j\) 变成 \(a_i\) 的最小操作次数。
我们对每一个数都这样做,发现这就是一个 bfs 的过程。这样就可以得到从 \(0\) 到 \(2^C - 1\) 之间的每一个数变成已有数字的最小操作次数,现在我们只需要枚举一遍所有 \(b_i\) 就能求出答案了。
我写还是比较详细的(也许有些啰嗦?)但是可能还是有些抽象,还有不懂的话可以结合代码理解。
代码
#include <bits/stdc++.h>
using namespace std;const int MN = 1e5 + 3;
int C, n;
int a[MN], rec[1 << 18];queue<pair<int, int> > q;int main() {ios::sync_with_stdio(0);cin.tie(0);cin >> C >> n;memset(rec, -1, sizeof(rec));for (int i = 1; i <= n; i++) {for (int j = 1; j <= C; j++) {char c;cin >> c;a[i] = (a[i] << 1) + (c == 'G' ? 0 : 1);}rec[a[i]] = 0;q.push({a[i], 0});}while (!q.empty()) {auto [x, cnt] = q.front();q.pop();for (int i = 0; i < C; i++) {int y = x ^ (1 << i);if (rec[y] < 0) q.push({y, rec[y] = cnt + 1});}}for (int i = 1; i <= n; i++)cout << C - rec[(1 << C) - 1 - a[i]] << "\n";return 0;
}
念念碎
本次模拟赛又双叒叕大 bear 归,非常的心痛,故作此文缓解一下。
你知道的,我没有那么聪明,所以本篇文章是在学习这篇题解后写的,拜谢大佬。