题解:洛谷 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

【核心思想】

  1. 问题分析:求第 \(N\) 小的"好整数"——可以由一个或多个 \(2\) 的幂(\(1, 2, 4, 8, 16, \ldots\))的字符串拼接而成的正整数。这是一个BFS/优先队列生成 + 去重问题,核心在于按数值大小顺序生成所有合法的拼接结果。

  2. 算法选择:

    • 小根堆(优先队列)+ BFS 扩展:从初始的 \(2\) 的幂开始,每次取出最小值,将其与所有 \(2\) 的幂拼接生成新数,放入堆中。利用 unordered_set 去重,保证每个数只被生成一次
  3. 关键步骤:

    • 预处理:生成所有 \(2\) 的幂的字符串表示(\(1, 2, 4, 8, \ldots, \leq 10^9\))
    • 初始化堆:将所有 \(2\) 的幂作为初始元素放入小根堆,同时标记已访问
    • BFS 扩展(循环 \(N\) 次):
      • 取出堆顶最小值 \(ans\)
      • 将 \(ans\) 的字符串表示与每个 \(2\) 的幂字符串拼接
      • 若拼接结果 \(\leq 10^9\) 且未访问过,标记访问并放入堆中
    • 输出结果:第 \(N\) 次取出的 \(ans\)
  4. 时间/空间复杂度:

    • 时间复杂度:\(O(N \cdot P \cdot \log (N \cdot P))\),\(P\) 为 \(2\) 的幂的个数(约 \(30\) 个),每次堆操作 \(O(\log (N \cdot P))\)
    • 空间复杂度:\(O(N \cdot P)\),存储已生成的数和访问标记
  5. 优先队列 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
posted @ 2026-08-26 10:12  团爸讲算法  阅读(7)  评论(0)    收藏  举报