Count Binary Palindromic Numbers

Count Binary Palindromic Numbers

You are given a non-negative integer n.

non-negative integer is called binary-palindromic if its binary representation (written without leading zeros) reads the same forward and backward.

Return the number of integers k such that 0 <= k <= n and the binary representation of k is a palindrome.

Note: The number 0 is considered binary-palindromic, and its representation is "0".

Example 1:

Input: n = 9

Output: 6

Explanation:

The integers k in the range [0, 9] whose binary representations are palindromes are:

  • 0 → "0"
  • 1 → "1"
  • 3 → "11"
  • 5 → "101"
  • 7 → "111"
  • 9 → "1001"

All other values in [0, 9] have non-palindromic binary forms. Therefore, the count is 6.

Example 2:

Input: n = 0

Output: 1

Explanation:

Since "0" is a palindrome, the count is 1.

 

Constraints:

  • 0 <= n <= 1015

 

解题思路

  之前好不容易学会了数位 dp 的写法,结果给我来了个求回文数的数位 dp,赛时磨了一个多小时嗯是没写出来,评价是烂完了。涉及到回文数的话,如果硬套 dfs 的模板貌似是做不了的,只能用较为复杂的递推写法。

  令 $c = \left\lfloor \log{n} \right\rfloor + 1$ 表示 $n$ 的二进制位数,在下面的讨论中,我们规定最低位为 $0$,最高位是 $c-1$。因为要求的数需要在二进制下是回文的,所以当确定了最高的 $\left\lceil c/2 \right\rceil$ 个二进制位后,剩下的 $\left\lfloor c/2 \right\rfloor$ 个二进制位就自动确定了,即 $b_i = b_{c-1-i} \left( 0 \leq i < \left\lfloor c/2 \right\rfloor \right)$,其中 $b_i \in \{0,1\}$ 表示第 $i$ 位填的数。另外需要注意的是,只有 $b_{c-1} = 1$ 时,才能得到恰好有 $c$ 位的二进制数。

  而当二进制位小于 $c$ 时,显然这些数对应的十进制数一定小于 $n$,因此我们首先计算位数小于 $c$ 的二进制回文数的数量。由于最高位一定是 $1$,因此我们可以单独考虑 $0$。如果只有 $1$ 个二进制位,则只有 $(1)_2$ 是回文数。如果有 $2$ 个二进制位,则回文数为 $(11)_2$。如果有 $3$ 个二进制位,则回文数有 $(101)_2$ 和 $(111)_2$。如果有 $4$ 个二进制位,则回文数有 $(1001)_2$ 和 $(1111)_2$。以此类推。可以发现,对于 $m$ 个二进制位,与上面的分析一样,当确定了最高的 $\left\lceil m \right\rceil$ 个二进制位后,剩下的 $\left\lfloor m \right\rfloor$ 个二进制位也自然确定了。在最高的 $\left\lceil m/2 \right\rceil$ 个位中,除了 $b_{m-1} = 1$ 外,其余位可以自由选择 $0$ 和 $1$。所以 $m$ 位二进制回文数的数量为 $2^{\left\lceil m/2 \right\rceil - 1}$。小于 $c$ 位数的回文数数量为 $\sum\limits_{m=1}^{c-1}{2^{\left\lceil m/2 \right\rceil - 1}}$。

  接下来我们再考虑二进制位数恰好为 $c$ 的情况,此时需要用到类似数位 dp 的递推方法。我们只考虑最高的 $\left\lceil c/2 \right\rceil$ 个二进制位(即 $b_{c-1}, \ldots, b_{c - \left\lceil c/2 \right\rceil} = b_{\left\lfloor c/2 \right\rfloor}$)填什么,并从第 $c-2$ 位开始依次往低位填(因为最高位 $b_{c-1}$ 必然是 $1$)。当枚举到第 $i$ 位时,$b_{c-1}, \ldots, b_{i+1}$ 已经确定,且与 $n$ 的二进制(最高 $c-i$ 位)表示相同,此时我们需要确保回文数对应的十进制不超过 $n$。如果 $n$ 的第 $i$ 位是 $0$,那么 $b_i$ 只能填 $0$。

  否则如果 $n$ 的第 $i$ 位是 $1$,那么 $b_i$ 可以填 $0$ 或 $1$。如果 $b_i = 0$,则不管 $b_{i-1}, \ldots, b_{\left\lfloor c/2 \right\rfloor}$ 填什么,最终得到的回文数对应的十进制一定小于 $n$,对应的数量为 $2^{i - \left\lfloor c/2 \right\rfloor}$。如果 $b_{i} = 1$,则继续向下枚举,贴紧上界。

  这部分对应的总数为 $\sum\limits_{i=\left\lfloor c/2 \right\rfloor}^{c-2}{\left[n_i=1\right] \, 2^{i - \left\lfloor c/2 \right\rfloor}}$,其中 $n_i$ 表示 $n$ 在二进制下第 $i$ 位的数。

  在枚举完后,此时 $b_{c-1}, \ldots, b_{\left\lfloor c/2 \right\rfloor}$ 已经贴紧上界,再令 $b_i = b_{c-1-i} \left( 0 \leq i < \left\lfloor c/2 \right\rfloor \right)$,然后将 $b_{c-1} \ldots b_{0}$ 所表示的十进制数与 $n$ 比较,如果不超过 $n$ 则 $b_{c-1} \ldots b_{0}$ 也是一个合法的二进制回文数。

  AC 代码如下,时间复杂度为 $O(\log{n})$:

class Solution {
public:
    int countBinaryPalindromes(long long n) {
        if (!n) return 1;
        int c = __lg(n) + 1, ret = 1;
        for (int i = 1; i < c; i++) {
            ret += 1 << (i + 1 >> 1) - 1;
        }
        for (int i = c - 2; i >= c >> 1; i--) {
            if (n >> i & 1) ret += 1 << i - c / 2;
        }
        long long t = n >> c / 2;
        for (int i = c + 1 >> 1; i < c; i++) {
            t = t << 1 | n >> i & 1;
        }
        ret += t <= n;
        return ret;
    }
};

  2025-09-11 更新:添加了一道求回文数的题,做法与上述十分类似。该题目来自“2025现代汽车前瞻技术研发急速编程决赛”,由于被设为私密无法给出链接,这里仅提供题面参考。

 

美丽的回文数加强版

Tk 定义一个不含前导零的整数 $x$ 是美丽的回文数,它要同时满足:

  • 它是一个回文数。
  • 它任意相邻两位奇偶性都不同。

Tk 会进行 $T$ 次询问,每次询问给你一个区间 $[l, r]$,你需要告诉他这个区间中有多少数是美丽的回文数,由于这个数很大你只需要输出对 $10^9 + 7$ 取模的结果即可。

输入描述

第一行输入一个整数 $T \left(1 \le T \le 10^4 \right)$ 表示询问次数。

接下来 $T$ 行每一行输入两个整数 $l, r \left(1 \le l \le r \le 10^{100}\right)$ 表示 Tk 询问的区间。

输出描述

输出分 $T$ 行每一行输出一个整数表示区间 $[l, r]$ 中有多少美丽的回文数,由于这个数很大你只需要输出对 $10^9 + 7$ 取模的结果即可。

示例 1

输入

3
1 5
2 16
5 103

输出

5
8
6

 

解题思路

  我们可以分别求出区间 $[1,r]$ 和 $[1,l-1]$ 中的美丽回文数的数量,然后作差得到 $[l,r]$ 中的美丽回文数的数量。因此问题变成了如何求出区间 $[1,n]$ 中的美丽回文数的数量。与上一题不同,这里除了要求是回文数,还要求相邻两位的奇偶性不同。

  首先可以确定的是,满足要求的回文数的位数一定是奇数。否则如果位数是偶数,由于回文数的中间两位数是相同的,导致奇偶性也相同,矛盾了。因此我们只需考虑位数是奇数的回文数。令 $c = \left\lfloor \log_{10}{n} \right\rfloor + 1$ 表示 $n$ 的位数。我们先求出位数小于 $c$ 的回文数的数量,这些回文数一定小于 $n$。假设回文数的位数是 $m$,我们只需确定最高的 $\left\lceil m/2 \right\rceil$ 位的数字。其中最高位一定要大于 $0$,因此可以从 $[1,9]$ 中选,剩余的 $\left\lceil m/2 \right\rceil - 1$ 个位可以从 $[0,9]$ 中选,但同时还要满足相邻两位的奇偶性不同。如果前一位是奇数,那么当前位只能选偶数,有 $5$ 种选择。如果前一位是偶数,那么当前位只能选奇数,也有 $5$ 种选择。因此 $m$ 位合法回文数的数量为 $9 \cdot 5^{\left\lceil m/2 \right\rceil - 1}$。

  如果 $c$ 是奇数,我们还需要考虑位数恰好为 $c$ 的回文数。考虑枚举最高位的前 $\left\lfloor c/2 \right\rfloor + 1$ 位,当枚举到第 $i$ 位时,前 $i-1$ 位需紧贴上界,第 $i$ 位可以从 $[0, n_i]$ 选,其中 $n_i$ 表示 $n$ 的第 $i$ 位上的数(如果是最高位则只能从 $[1,n_i]$ 中选)。此外,第 $i$ 位的数字必须与前一位($n_{i-1}$)的奇偶性不同。如果第 $i$ 位不等于 $n_i$,则后续的 $\left\lceil c/2 \right\rceil - i - 1$(或 $\left\lfloor c/2 \right\rfloor - i$)位可以任选,有 $5^{\left\lceil c/2 \right\rceil - i - 1}$ 种合法选择。如果第 $i$ 位等于 $n_i$,则需要确保其奇偶性与 $n_{i-1}$ 不同,否则无法继续保持贴紧上界,枚举过程中断。

  最后记得判断 $n_0 \ldots n_{\left\lfloor \log_{10}{n} \right\rfloor - 1} \, n_{\left\lfloor \log_{10}{n} \right\rfloor} \, n_{\left\lfloor \log_{10}{n} \right\rfloor - 1} \ldots n_0$ 构成的十进制数是否是美丽回文数,且不超过 $n$。

  AC 代码如下,时间复杂度为 $O(\log{n})$:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 105, mod = 1e9 + 7;

int p[N];

void init() {
    p[0] = 1;
    for (int i = 1; i < N; i++) {
        p[i] = 5ll * p[i - 1] % mod;
    }
}

string sub(string s) {
    reverse(s.begin(), s.end());
    for (int i = 0; i < s.size(); i++) {
        if (s[i] > '0') {
            s[i]--;
            break;
        }
        s[i] = '9';
    }
    while (s.size() > 1 && s.back() == '0') {
        s.pop_back();
    }
    reverse(s.begin(), s.end());
    return s;
}

int get(string s) {
    if (s[0] == '0') return 0;
    int ret = 0;
    for (int i = 1; i < s.size(); i += 2) {
        ret = (ret + 9ll * p[(i + 1 >> 1) - 1]) % mod;
    }
    if (s.size() & 1) {
        for (int i = 0; i <= s.size() >> 1; i++) {
            for (int j = !i; j < s[i] - '0'; j++) {
                if (i && !(j - s[i - 1] & 1)) continue;
                ret = (ret + p[(s.size() + 1 >> 1) - i - 1]) % mod;
            }
            if (i && !(s[i] - s[i - 1] & 1)) return ret;
        }
        string t = s.substr(0, s.size() >> 1);
        reverse(t.begin(), t.end());
        t = s.substr(0, s.size() + 1 >> 1) + t;
        ret += t <= s;
    }
    return ret;
}

void solve() {
    string a, b;
    cin >> a >> b;
    cout << (get(b) - get(sub(a)) + mod) % mod << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    init();
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    
    return 0;
}

 

参考资料

  单调栈 回文数【力扣周赛 466】:https://www.bilibili.com/video/BV1heYGzWEUa/

posted @ 2025-09-10 23:21  onlyblues  阅读(31)  评论(0)    收藏  举报
Web Analytics