B4529 信息与未来 2026 枚举 / 奇偶 / bitmask

幸运数字:让出现次数自己开关灯

5-10 分钟讲清楚一个模型:数字出现一次,对应 bit 翻转;出现两次,翻回去;全部熄灭时 mask = 0。

一、先回答问题

直接结论

枚举区间里的每个数。检查一个数时,不需要真的开 10 个计数器,只需要一个 10 位二进制 mask 记录每个数字出现次数的奇偶。

读到数字 d:mask ^= (1 << d)
课堂安排

先用开关灯理解「一次翻转、两次抵消」,再手推几个候选数,最后把这个动作对应到 C++ 代码里的 mask ^= (1 << d)

本题上界只有 1,000,000,枚举每个数再扫它的十进制位完全足够。

二、核心观察

数字第一次出现 0 → 1
数字第二次出现 1 → 0
最后判断 mask == 0
为什么只看奇偶

幸运数字要求「每种数字都恰好出现偶数次」。所以 2 次、4 次、6 次在答案上没有区别,都是偶数;1 次、3 次、5 次也没有区别,都是奇数。

bitmask 的每一位只回答一个问题:这个数字当前出现了奇数次吗?如果所有位都是 0,说明没有任何数字出现奇数次。

三、互动演示:点一个数字,翻转一盏灯

bitmask 实验台

选择候选数,然后按顺序点击每个数字。蓝灯亮表示这个数字目前出现了奇数次;再点同一个数字,灯会灭。

10 个 bit 对应数字 0 到 9
当前候选数11
mask 二进制0000000000
已扫描0 / 2
还没有扫描数字。每扫到一个数字 d,就翻转第 d 位。

四、自己选答案

判断 1

1221 是幸运数字吗?

先想 1 和 2 各出现了几次。
判断 2

扫完一个数后,mask = 0000001000,第 3 位亮。这个数能计入答案吗?

只要有一盏灯亮,就有某个数字出现奇数次。

五、手推结果

候选数 翻转过程 最后 mask 判断
11 1 亮,1 再灭 全 0 幸运数字
23333 2 亮;3 亮、灭、亮、灭 第 2 位亮 不是
131131 1 出现 4 次,3 出现 2 次 全 0 幸运数字
1 到 100 两位相同的数:11、22、...、99 每个候选都全 0 共有 9 个

六、C++14 参考代码

本地 tok-* 高亮,无 CDN
#include <bits/stdc++.h>
using namespace std;

bool lucky(int x) {
    int mask = 0;
    while (x > 0) {
        int d = x % 10;
        mask ^= (1 << d);
        x /= 10;
    }
    return mask == 0;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int a, b;
    cin >> a >> b;

    int ans = 0;
    for (int x = a; x <= b; x++) {
        if (lucky(x)) ans++;
    }
    cout << ans << '\n';
    return 0;
}

七、容易错在哪里

误区 1

把“出现过”当成条件。幸运数字不是每种数字都出现,而是出现过的数字次数必须为偶数。

误区 2

忘记数字 0 也有自己的 bit。比如 1001 中,0 出现 2 次,所以它不会破坏答案。

误区 3

以为必须存完整计数。本题只问奇偶,mask 足够表达会影响答案的全部信息。