幂和数:逆向构造与直接计数
给定正整数区间 [l, r],要求统计该区间内有多少个可以表示为两个 2 的非负整数次幂之和的整数(即幂和数)。
题库1分钟阅读
对于正整数 ,如果 可以表为两个 的次幂之和,即 ( 均为非负整数),那么称 为幂和数。
给定正整数 ,请你求出满足 的整数 中有多少个幂和数。
输入格式
一行,两个正整数 ,含义如上。
输出格式
输出一行,一个整数,表示 之间幂和数的数量。
样例
输入样例 1
2 8输出样例 1
6输入样例 2
10 100输出样例 2
20数据范围
对于所有测试点,保证 。
对于这道题,最直观的想法是遍历 区间内的每一个数字 ,然后再去判断 能不能拆成两个 的次幂之和。但这种“正向检查”不仅逻辑繁琐,效率也较低。
我们可以换个角度逆向思考:
- 题目要求的数具有特定的结构:。
- 在数据范围 下, 的次幂增长极快(),能用到的加数不超过 15 个!
- 结论:既然符合条件的加数少之又少,不如直接把所有可能的加数拼起来,构造出所有的幂和数,再看它们是否落在 区间内。
定义第一个加数为 ,第二个加数为 。
我们可以让变量从 开始,每次通过 *= 2 递推:
- 的变化轨迹:
- 的变化轨迹:
如果无脑双重循环枚举 和 ,算出来的 和 会造成重复计数。
为了避免这种顺序上的重复,我们强制规定 (即 ):
- 让内层循环的 直接从 的当前值开始递增枚举(
b = a)。 - 数学性质保证:对于任意两个不相等的次幂组合,二进制下的 出现的位置不同,因此只要限定 ,每一个唯一的二进制幂和数就只会被构造出来恰好一次。
- 输入区间边界 和 ,初始化答案计数器
ans = 0。 - 外层循环:枚举第一个加数 ,从 开始,只要 ,每次循环后 乘以 。
- 内层循环:枚举第二个加数 ,从 开始(限定 防重),只要 ,每次循环后 乘以 。
- 计算与判断:
- 计算构造出的幂和数 。
- 如果 ,说明这个数落在目标区间内,让
ans++。
- 循环结束,输出
ans。
#include <iostream>
using namespace std;
int main() { // 优化输入输出流 ios::sync_with_stdio(false); cin.tie(nullptr);
int l, r; if (!(cin >> l >> r)) return 0;
int ans = 0;
// 1. 枚举第一个加数 a = 2^x for (int a = 1; a <= r; a *= 2) { // 2. 枚举第二个加数 b = 2^y // 注意:令 b 从 a 开始,即限定 b >= a (x <= y),彻底解决顺序重复问题 for (int b = a; b <= r; b *= 2) { int n = a + b;
// 3. 判断构造出的幂和数是否落在目标区间内 if (n >= l && n <= r) { ans++; } } }
cout << ans << "\n";
return 0;}- 时间复杂度:
- 外层循环次数约等于 (当 时约为 14 次)。
- 内层循环次数更少。
- 总循环次数小于 次,时间复杂度为 ,在任何评测机上都是 0 毫秒秒杀。
- 空间复杂度:
- 只用到了几个基础整型变量,空间复杂度为 ,极度节省内存。