- 考点:二分查找 + 数位统计
- 题目描述:

- 解题思路1:挨个统计
python
import sys
def main():
x = int(sys.stdin.readline().strip())
total_1 = 0 # 统计1的总个数
total_len = 0 # 已经拼接的总长度
num = 0 # 从0开始拼接!
while total_len < x:
# 1. 把当前数字转成二进制字符串(去掉0b)
binary = bin(num)[2:]
# 2. 还能截取多少位:最多取到x位,不超额
need = x - total_len
# 3. 截取当前二进制的前 need 位
cut = binary[:need]
# 4. 统计这一段里的1,加到总数
total_1 += cut.count('1')
# 5. 更新总长度
total_len += len(cut)
# 6. 下一个数字
num += 1
return total_1
# 主程序
if __name__ == "__main__":
main()
- 解题思路2:
目标:前 n 位里有多少个 1
问题拆分:定位 + 统计
- 定位,前 n 位是**"哪些完整数字的二进制" + "最后一个不完整数字的前 offset 位"**
如何定位?------二分查找:找到最大的数字 target,使得前 target 个数字的二进制总长度 < n
- 统计,"完整数字中 1 的总数" + "最后一个数字前 offset 位中 1 的个数"
如何统计?------数位贡献:各数字二进制的第 i 位,每 个数为一个周期,每个周期中有
个 1,且前半周期是 0,后半周期是 1

- 核心函数思路:返回 [0,n] 拼接成二进制串后的长度和其中 1 的个数
- 总长度
按位分段:除了0以外,二进制有1位的有 个数字、2位的有
个、3位的有
个、4位的有
个、...k位的有
个,这个值也表示二进制有k位的最小数字 ,则二进制有k位的数字总长度为
× k
知道这个规律后,可以先获取数字 n 的二进制位数 bits_count,然后累加计算 1 到 bits_count-1 位的总长度
python
bits_count = n.bit_length()
for i in range(1, bits_count):
# i位二进制数的个数
cur_num = 2 ** (i-1)
length += cur_num * i
接着计算位数为 bits_count,到数字 n 截止的长度
python
# 截止到 n 的 bits_count 位的数字个数
now_num = n - (2 ** (bits_count - 1)) + 1
length += now_num * bits_count
- 含1个数
数位贡献:根据前文分析,累加计算各周期中1的个数
python
bits_count = n.bit_length()
for i in range(bits_count):
# 周期长度 = 2^(i+1)
period = 2 ** (i+1)
# 每个周期内第i位为1的次数 = 2^i
period_count = 2 ** i
# 完整周期数
full = (n+1) // period
count += full * period_count
# 不完整周期的有效个数
not_full = (n+1) % period
if not_full > period_count:
count += (not_full - period_count)
- 参考代码:
python
import sys
# 计算数字[0,n)拼接成二进制串后的长度,以及其中1的个数
def get_info(n):
if n <= 0:
return 0,0
if n == 1:
return 1,0
m = n - 1
length = 1
count = 0
# 统计1的个数,数位贡献
# m的二进制位数
bits_count = m.bit_length()
for i in range(bits_count):
# 周期长度 = 2^(i+1)
period = 1 << (i+1)
# 每个周期内第i位为1的次数 = 2^i
period_count = 1 << i
# 完整周期数
full = (m+1) // period
count += full * period_count
# 不完整周期的有效个数
not_full = (m+1) % period
if not_full > period_count:
count += (not_full - period_count)
# 统计总长度,按位数分段,遍历i~bits_count-1位
for i in range(1, bits_count):
# i位二进制数的个数
cur = 1 << (i-1)
length += cur * i
# 计算bits_count位的数字个数
cur = m - (1 << (bits_count - 1)) + 1
length += cur * bits_count
return length, count
def main():
x = int(sys.stdin.readline().strip())
if x == 1:
print(0)
return
# 二分查找定位目标数字:找到第 x 位在哪个数字 target 的二进制中
# 逻辑:找最大的 target,使得前 target 个数字的长度<x
left = 1
# 将x作为上界
right = x
target = 0
while left <= right:
mid = (left + right) // 2
# 取[0,mid)的总长度,忽略1的个数,_表示占位符
length, _ = get_info(mid)
if length < x:
# 总长度不足,mid符合条件,目标在更大的数字里
target = mid
left = mid+1
else:
# 长度超过或与x相等,缩小上界
right = mid-1
# 二分查找完毕,此时target是最后一个总长度小于x的数字
len1, sum1 = get_info(target)
offset = x - len1
bin_target = bin(target)[2:]
ans = sum1 + bin_target[:offset].count('1')
print(ans)
if __name__ == '__main__':
main()
- 位运算:1 << k,等同于
原理:二进制中左移一位,本质上就是乘以2
最好用位运算而不是幂运算 ** :位运算是 CPU 底层直接支持的硬件指令,速度快,而幂运算本质上是一个函数调用