Required Length

GitHub跳转原题关系图返回列表

当前整数即完整状态 + 剪掉数位 0/1 与越界分支 + 去重 BFS 首次到达即最短

OJ: codeforces

题目 ID: 1681D

难度:普及+/提高-

标签:BFS搜索剪枝状态搜索

创建: 2026-11-04 16:26

更新: 2026-11-06 23:07

RBook 文章:
广度优先搜索

形式化题目

给定两个整数 nn 与初值 x0x_0,其中 x0x_0 的十进制位数少于 nn。允许重复执行下面这一种操作:

  • 从当前整数的十进制表示中任选一个出现过的数位 dd,把当前整数替换为它的 dd 倍,即 x←x⋅dx \leftarrow x \cdot d。

要求让当前整数的十进制位数(不含前导零)恰好等于 nn,即让 10n−1⩽x⩽10n−110^{n-1} \leqslant x \leqslant 10^{n} - 1。求所需的最少操作次数;若无论怎样操作都达不到 nn 位,则判定为不可达。

正解

思路

能乘哪些数位完全由当前整数决定,所以「当前整数」就是完整状态,把它当点、一次乘法当边,边权都是 11,问题就是无权图最短路。用 BFS 按层扩展,队列里存 (当前整数, 步数),第一次走到 nn 位数时的步数就是最少步数。剪枝只需三条:乘 00 之后永远是 00,乘 11 不改变整数,这两种数位直接跳过;每一步都乘 ⩾2\geqslant 2,位数只增不减,所以位数超过 nn 的后继直接丢掉。同一个整数能经不同顺序乘出来,用 vis 去重,只展开第一次到达即可,BFS 首次到达即最短。状态数也有上界:乘数只含质因子 2,3,5,72,3,5,7,可达状态都是 x0⋅2a3b5c7dx_0 \cdot 2^a3^b5^c7^d,n=19n = 19 时这样的数不到 10510^5 个,搜索不会爆炸。

代码

python
import sys
from collections import defaultdict, deque

## 队列元素:当前整数,已经用掉的步数
type Item = tuple[int,int]


# 返回 n 的十进制位数(不含前导零)
def num_len(n):
    return len(str(n))


# 逐位取出 n 的十进制数位:37 -> 7, 3
def num_iter(n):
    while n :
        yield n % 10
        n //= 10



def solve(data):
    n = next(data)      # 目标位数
    num = next(data)    # 初值 x
    q= deque()          # BFS 队列,按层扩展
    q.append((num,0))   # 初值入队,步数为 0
    vis = defaultdict(bool)  # 访问集合:记录已入队过的整数,防止重复展开
    ans = -1;           # 默认无解,输出 -1

    while q:
        cur,step = q.popleft()   # 队头出队:当前整数与已用步数
        # print(cur,step)
        for i in num_iter(cur):  # 枚举 cur 出现过的每一个数位
            if i ==0 or i == 1 : # 剪枝:乘 0 归零卡死,乘 1 不改变整数
                continue
            nxt = cur * i        # 一步操作:乘上数位 i
            # print(f"nxt({nxt}) = {cur} * {i}")
            if num_len(nxt) > n: # 剪枝:位数已超过 n,之后回不来
                continue
            if num_len(nxt)  == n:   # 首次命中 n 位,BFS 保证步数最少
                ans = step + 1
                print(ans)
                return
            if vis[nxt] == False:    # 去重:只展开第一次到达的状态
                vis[nxt] = True
                q.append((nxt,step+1))
    print(ans)                       # 队列耗尽仍未命中,无解
    
    


if __name__ == "__main__":
    data = iter(map(int, sys.stdin.buffer.read().split()))  # 顺序读入两个整数
    solve(data)

复杂度

设 SS 为实际访问的状态数。每个状态要拆出至多 nn 位并做常数次乘法,单状态开销 O(n)O(n)。

  • 时间复杂度:O(S⋅n)O(S \cdot n),n=19n = 19 时 SS 的量级约 10410^4。
  • 空间复杂度:O(S)O(S),队列与访问集合各存 SS 个整数。

总结

  • 「当前整数」就是完整状态,问题化为无权图最短路,BFS 分层求解,首次碰到 nn 位数即答案。
  • 数位 00 会卡死在 00,数位 11 是空操作,位数超过 nn 回不来,这三类分支直接剪掉。
  • 用 vis 去重后每个状态只展开一次;乘数只含质因子 2,3,5,72,3,5,7,状态总数可控。