枚举区间里的每个数。检查一个数时,不需要真的开 10 个计数器,只需要一个 10 位二进制 mask 记录每个数字出现次数的奇偶。
读到数字 d:mask ^= (1 << d)
5-10 分钟讲清楚一个模型:数字出现一次,对应 bit 翻转;出现两次,翻回去;全部熄灭时 mask = 0。
枚举区间里的每个数。检查一个数时,不需要真的开 10 个计数器,只需要一个 10 位二进制 mask 记录每个数字出现次数的奇偶。
先用开关灯理解「一次翻转、两次抵消」,再手推几个候选数,最后把这个动作对应到 C++ 代码里的 mask ^= (1 << d)。
本题上界只有 1,000,000,枚举每个数再扫它的十进制位完全足够。
幸运数字要求「每种数字都恰好出现偶数次」。所以 2 次、4 次、6 次在答案上没有区别,都是偶数;1 次、3 次、5 次也没有区别,都是奇数。
bitmask 的每一位只回答一个问题:这个数字当前出现了奇数次吗?如果所有位都是 0,说明没有任何数字出现奇数次。
选择候选数,然后按顺序点击每个数字。蓝灯亮表示这个数字目前出现了奇数次;再点同一个数字,灯会灭。
1221 是幸运数字吗?
扫完一个数后,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 个 |
#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;
}
mask ^= (1 << d) 是整题最核心的一句:第 d 位从 0 到 1,或从 1 到 0。while (x > 0) 每次取末位数字,顺序不影响结果,因为我们只关心翻转次数的奇偶。把“出现过”当成条件。幸运数字不是每种数字都出现,而是出现过的数字次数必须为偶数。
忘记数字 0 也有自己的 bit。比如 1001 中,0 出现 2 次,所以它不会破坏答案。
以为必须存完整计数。本题只问奇偶,mask 足够表达会影响答案的全部信息。