文章

【GESP】C++三级真题 luogu-B4499, [GESP202603 三级] 二进制回文串

2026年3月,GESP三级真题,考察十进制转二进制与回文判断,难度★★☆☆☆。

B4499 [GESP202603 三级] 二进制回文串

题目要求

题目描述

对于一个正整数 $n$,我们将其转换为不含前导零的二进制表示,如果这个二进制序列从左向右读与从右向左读完全相同,则称该数为二进制回文数。例如,$9$ 的二进制表示为 $(1001)_2$,是二进制回文数;$12$ 的二进制表示为 $(1100)_2$,不是二进制回文数。

你的任务是:给定一个正整数 $n$,计算在 $1$ 到 $n$ 的范围内二进制回文数的数量。

输入格式

输入一行,包含一个正整数 $n$。

输出格式

输出一行,包含一个数,表示在 $1$ 到 $n$ 的范围内二进制回文数的数量。

输入输出样例 #1

输入 #1

1
15

输出 #1

1
6

说明/提示

样例解释 样例 1 中,$1$ 到 $15$ 范围内 $1$、$3$、$5$、$7$、$9$、$15$ 是二进制回文数。

数据范围 $1 \le n \le 10^5$。


题目分析

这道题考察了两个重要且经典的知识点:进制转换回文判断。GESP 三级对数组的掌握有了明确的要求,这道题恰好可以把这些点结合起来。

题目的核心是遍历 $1$ 到 $n$ 的每一个数字,然后判断该数字是不是“二进制回文数”。如果是,计数器就加一,最后输出计数器的值。

如何判断一个整数是不是“二进制回文数”呢?

三级传统解法

  1. 转二进制并提取各位数字: 对于一个十进制数,我们可以不断地对 2 实行“求余”和“整除”操作(也就是“除2取余法”),每次取出的余数就是二进制下该位的值。我们可以用一个整数数组(比如 int bin[40])把这些提取出来的余数依次存起来。
  2. 回文判断: 提取完成后,这个数组里存放的就是该数转换出的二进制序列的逆序(先求出的是最低位)。但巧妙的是,“回文”的定义正着读和反着读都完全一样,所以我们就算存的是个逆序数组,也不影响回文的判定。我们使用“双指针”的办法,一个从数组的头(下标 0)开始,一个从数组的尾(下标 len - 1)开始,两头向中间靠拢,一一比对。只要有不一样的地方,就说明不是回文;如果直到中间都完全一样,那就是回文。

由于在 GESP 三级考纲中,尚未要求掌握自定义函数,我们在处理每位数字时,可直接将上述判定逻辑内嵌在主循环中完成。

拓展解法(位运算)

如果熟练运用 C++ 的位运算(Bitwise Operations),这道题能够做到甚至不借用任何数组,仅在原数字上进行“变魔术”就能判断出结果。位运算也在 GESP 三级考纲中有明确要求,但通常使用的较少切容易被忽略,但本体使用起来非常巧妙。

我们可以想办法“捏造”出一个新的反转数字 reversed。就像组装十进制数字一样(个位乘1,十位乘10),这次我们在二进制的维度中组装它。只需要不断地把已有的翻转数字通过 << 1 左移腾出空间,然后“贴上”原数最末尾的那一位(也就是 x & 1),最后将原数 >> 1(右移)用来抛弃已经被处理过的低位。当这个过程重复完毕,直接拿还原出倒转数字和原数对比即可。非常简洁!

示例代码

传统解法(推荐)

按照上述分析,使用“数组存储二进制各位”结合“首尾双指针比对”是十分符合 GESP 三级大纲要求的经典解法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>

int main() {
    int n;
    std::cin >> n;

    int count = 0; // 用于统计回文数的数量

    // 从 1 到 n 挨个检查
    for (int i = 1; i <= n; i++) {
        int x = i;
        int bin[40]; // 准备一个数组用来存放数字的二进制位,10^5 足够用了
        int len = 0; // 记录二进制的位数

        // 不断地进行除 2 取余,将生成的二进制位存入数组
        while (x > 0) {
            bin[len] = x % 2;
            x = x / 2;
            len++;
        }

        // 双指针进行回文判断
        bool is_palindrome = true;
        // j 从 0 开始往后走,k 从末尾往前走,直到两者相遇或者错过
        for (int j = 0, k = len - 1; j < k; j++, k--) {
            if (bin[j] != bin[k]) {
                // 只要有一对不同,这就绝不可能是回文串
                is_palindrome = false;
                break;
            }
        }

        // 如果经受住了所有比对,那就是回文串
        if (is_palindrome) {
            count++;
        }
    }

    std::cout << count << std::endl;

    return 0;
}

拓展解法(位运算)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>

bool isBinaryPalindromeBitwise(int x) {
    int original = x;  // 记录下它原本的值,等下用来最终比较
    int reversed = 0;  // 用来一点一点组装倒排序后的数字

    while (x > 0) {
        // reversed << 1 表示把已组装的数字整体左移(类似于十进制乘 10)
        // x & 1 取出最低位对应的二进制位(0 或 1,等同于 % 2)
        // | (按位或) 也就是把那一位“拼接”在最低位空白处
        reversed = (reversed << 1) | (x & 1);

        // x >>= 1 表示被取光的最末位可以丢弃了(类似于十进制除 10)
        x >>= 1;
    }

    // 翻转重建后的数如果和原数相等,那就是天然的回文数啦!
    return original == reversed;
}

int main() {
    int n;
    std::cin >> n;

    int count = 0;
    for (int i = 1; i <= n; i++) {
        if (isBinaryPalindromeBitwise(i)) {
            count++;
        }
    }

    std::cout << count << std::endl;

    return 0;
}

所有代码已上传至Github:https://github.com/lihongzheshuai/yummy-code

GESP 学习专题站:GESP WIKI

luogu-”系列题目可在洛谷题库进行在线评测。

bcqm-”系列题目可在编程启蒙题库进行在线评测。

欢迎加入Java、C++、Python技术交流QQ群(982860385),大佬免费带队,有问必答

欢迎加入C++ GESP/CSP认证学习QQ频道,考试资源总结汇总

欢迎加入C++ GESP/CSP学习交流QQ群(688906745),考试认证学员交流,互帮互助

GESP/CSP 认证学习微信公众号
GESP/CSP 认证学习微信公众号
本文由作者按照 CC BY-NC-SA 4.0 进行授权