2.8二进制位运算
题目P6306 「Wdsr-1」小铃的书
题目分析
-
核心问题:
- 小铃的书有
n-1
本,编号满足每种编号的书的数量是k
的倍数 - 混入了一本魔理沙的魔导书(编号未知),现在总共有
n
本书 - 我们需要找出这本魔导书的编号
- 小铃的书有
-
性质分析:
- 由于小铃的书编号数量满足倍数关系,每种编号的书的数量在
n-1
本书中均是k
的倍数 - 混入魔导书后,只有这一本书会导致某种编号的数量不再是
k
的倍数
- 由于小铃的书编号数量满足倍数关系,每种编号的书的数量在
-
等价描述:
- 对每一位的二进制位,统计所有书的编号中该位为
1
的书的个数 - 由于小铃的书的编号在每一位上的
1
的数量也是k
的倍数,混入魔导书后导致某些位的1
的数量多了1
- 对每一位的二进制位,统计所有书的编号中该位为
解法设计
-
利用二进制性质:
- 每个数可以分解为
64
位二进制表示 - 对每一位统计其
1
的总数量,检查是否是k
的倍数。如果不是倍数,则说明魔导书的该位为1
- 每个数可以分解为
-
空间和时间复杂度优化:
- 时间复杂度:对每个数处理
64
位二进制,复杂度为O(n *64)
,等价于O(n)
- 空间复杂度:只需要存储
64
位计数器,复杂度为O(64)
,即O(1)
- 时间复杂度:对每个数处理
代码实现
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
// 快速读取整数
inline ll read() {
ll x = 0; bool neg = false; char c = getchar();
while (!isdigit(c) && c != '-' && c != EOF) c = getchar();
if (c == '-') { neg = true; c = getchar(); }
while (isdigit(c)) { x = x * 10 + (c - '0'); c = getchar(); }
return neg ? -x : x;
}
int main() {
ll n = read(), k = read(); // 读取 n 和 k
int bc[64] = {0}; // 每一位的计数器(0~63)
// 遍历 n 个书的编号
for (ll i = 0; i < n; i++) {
ll x = read(); // 读取书的编号
for (int j = 0; j < 64; j++) {
if (x & (1LL << j)) // 检查第 j 位是否为 1
bc[j]++;
}
}
ll res = 0; // 结果
for (int j = 0; j < 64; j++) {
if (bc[j] % k != 0) // 如果第 j 位的数量不是 k 的倍数
res |= (1LL << j); // 将第 j 位设为 1
}
cout << res << '\n'; // 输出魔导书编号
return 0;
}
样例分析
样例 1:
输入:
10 3
1 1 2 2 3 5 3 2 1 3
输出:
5
解释:
- 小铃的书中编号为
1, 2, 3
的书分别有3
本,满足k=3
- 混入的魔导书编号为
5
,导致编号5
的书的数量变为1
,从而破坏倍数关系
样例 2:
输入:
13 4
1 1 4 5 1 4 1 4 4 5 5 5 1
输出:
1
解释:
- 小铃的书编号为
1, 4, 5
,分别有4
本,满足k=4
- 混入的魔导书编号为
1
,导致编号1
的书的数量多出1