当前整数即完整状态 + 剪掉数位 0/1 与越界分支 + 去重 BFS 首次到达即最短
OJ: codeforces
题目 ID: 1681D
难度:普及+/提高-
形式化题目
给定两个整数
- 从当前整数的十进制表示中任选一个出现过的数位
,把当前整数替换为它的 倍,即 。
要求让当前整数的十进制位数(不含前导零)恰好等于
正解
思路
能乘哪些数位完全由当前整数决定,所以「当前整数」就是完整状态,把它当点、一次乘法当边,边权都是 (当前整数, 步数),第一次走到 vis 去重,只展开第一次到达即可,BFS 首次到达即最短。状态数也有上界:乘数只含质因子
代码
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)复杂度
设
- 时间复杂度:
, 时 的量级约 。 - 空间复杂度:
,队列与访问集合各存 个整数。
总结
- 「当前整数」就是完整状态,问题化为无权图最短路,BFS 分层求解,首次碰到
位数即答案。 - 数位
会卡死在 ,数位 是空操作,位数超过 回不来,这三类分支直接剪掉。 - 用
vis去重后每个状态只展开一次;乘数只含质因子,状态总数可控。