题解:洛谷 AT_abc451_d Concat Power of 2
【题目来源】
洛谷:AT_abc451_d [ABC451D] Concat Power of 2 - 洛谷
【题目描述】
满足以下条件的正整数称为“好整数”:
选择 \(2\) 的幂(\(1,2,4,8,16,\dots\))中的一个或多个(允许重复,也允许改变顺序),将它们作为字符串拼接起来,再将得到的字符串解释为整数,即可得到该数。
请找出第 \(N\) 小的好整数。
保证第 \(N\) 小的好整数不超过 \(10^9\)。
【输入】
输入一个数 \(N\)
【输出】
输出一个数,第 \(N\) 小的好整数
【输入样例】
10
【输出样例】
21
【核心思想】
-
问题分析:求第 \(N\) 小的"好整数"——可以由一个或多个 \(2\) 的幂(\(1, 2, 4, 8, 16, \ldots\))的字符串拼接而成的正整数。这是一个BFS/优先队列生成 + 去重问题,核心在于按数值大小顺序生成所有合法的拼接结果。
-
算法选择:
- 小根堆(优先队列)+ BFS 扩展:从初始的 \(2\) 的幂开始,每次取出最小值,将其与所有 \(2\) 的幂拼接生成新数,放入堆中。利用
unordered_set去重,保证每个数只被生成一次
- 小根堆(优先队列)+ BFS 扩展:从初始的 \(2\) 的幂开始,每次取出最小值,将其与所有 \(2\) 的幂拼接生成新数,放入堆中。利用
-
关键步骤:
- 预处理:生成所有 \(2\) 的幂的字符串表示(\(1, 2, 4, 8, \ldots, \leq 10^9\))
- 初始化堆:将所有 \(2\) 的幂作为初始元素放入小根堆,同时标记已访问
- BFS 扩展(循环 \(N\) 次):
- 取出堆顶最小值 \(ans\)
- 将 \(ans\) 的字符串表示与每个 \(2\) 的幂字符串拼接
- 若拼接结果 \(\leq 10^9\) 且未访问过,标记访问并放入堆中
- 输出结果:第 \(N\) 次取出的 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N \cdot P \cdot \log (N \cdot P))\),\(P\) 为 \(2\) 的幂的个数(约 \(30\) 个),每次堆操作 \(O(\log (N \cdot P))\)
- 空间复杂度:\(O(N \cdot P)\),存储已生成的数和访问标记
-
优先队列 BFS 与字符串拼接的核心思想:
- 按序生成的保证:小根堆确保每次取出的都是当前未输出数中的最小值,天然满足"第 \(N\) 小"的要求,无需事后排序
- 状态扩展的完备性:每个好整数都可以看作某个更小的好整数后面拼接一个 \(2\) 的幂。从初始的 \(2\) 的幂出发,不断向后拼接,可以生成所有好整数
- 去重机制:
unordered_set防止同一数值通过不同拼接顺序被重复生成(如1+2=12和1+2只生成一次) - 长度剪枝:拼接后字符串长度超过 \(9\)(即 \(> 10^9\))时跳过,避免溢出且减少无效状态
- 字符串拼接的数值保持:将整数转为字符串拼接后再转回整数,确保拼接的语义正确(如
2+4=24而非6) - 适用于按序生成、状态空间搜索、字符串拼接组合类问题
【算法标签】
普及 #BFS-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n; // 要找到的第n个特殊数
vector<string> powers; // 存储2的幂的字符串形式
priority_queue<int, vector<int>, greater<int>> pq; // 小根堆,存储已生成的数
unordered_set<int> vis; // 已访问集合,去重
signed main()
{
cin >> n; // 输入n
// 生成2的幂的字符串表示
for (int i = 1; i <= 1e9; i *= 2) // 生成2^0到2^30
{
powers.push_back(to_string(i)); // 转换为字符串存储
}
// 初始化优先队列
for (auto &s : powers) // 遍历2的幂
{
int num = stoll(s); // 将字符串转回整数
if (!vis.count(num)) // 如果未访问过
{
vis.insert(num); // 标记为已访问
pq.push(num); // 加入小根堆
}
}
int ans = 0;
for (int i = 1; i <= n; i++) // 循环n次,找到第n小的数
{
ans = pq.top(); // 获取当前最小的数
pq.pop(); // 弹出最小数
string cur = to_string(ans); // 转换为字符串
for (auto &s : powers) // 尝试拼接每个2的幂
{
if (cur.length() + s.length() > 9) // 如果拼接后超过9位
{
continue; // 跳过,防止溢出
}
int nxt = stoll(cur + s); // 拼接并转换为整数
if (!vis.count(nxt)) // 如果未生成过
{
vis.insert(nxt); // 标记为已访问
pq.push(nxt); // 加入优先队列
}
}
}
cout << ans << endl; // 输出第n个数
return 0;
}
【运行结果】
10
21
浙公网安备 33010602011771号