Count Binary Palindromic Numbers
Count Binary Palindromic Numbers
You are given a non-negative integer n.
A 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/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/19084751

浙公网安备 33010602011771号