2.8二进制位运算

题目P6306 「Wdsr-1」小铃的书

题目分析

  1. 核心问题

    • 小铃的书有 n-1 本,编号满足每种编号的书的数量是 k 的倍数
    • 混入了一本魔理沙的魔导书(编号未知),现在总共有 n 本书
    • 我们需要找出这本魔导书的编号
  2. 性质分析

    • 由于小铃的书编号数量满足倍数关系,每种编号的书的数量在 n-1 本书中均是 k 的倍数
    • 混入魔导书后,只有这一本书会导致某种编号的数量不再是 k 的倍数
  3. 等价描述

    • 对每一位的二进制位,统计所有书的编号中该位为 1 的书的个数
    • 由于小铃的书的编号在每一位上的 1 的数量也是 k 的倍数,混入魔导书后导致某些位的 1 的数量多了 1

解法设计

  1. 利用二进制性质

    • 每个数可以分解为 64 位二进制表示
    • 对每一位统计其 1 的总数量,检查是否是 k 的倍数。如果不是倍数,则说明魔导书的该位为 1
  2. 空间和时间复杂度优化

    • 时间复杂度:对每个数处理 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
posted @ 2025-01-25 23:57  fufuaifufu  阅读(17)  评论(0编辑  收藏  举报