1. 问题描述:
满足如下条件的序列 X(序列中元素被标号为 1、2、3…m)被称为"加成序列":
X[1]=1
X[m]=n
X[1]<X[2]<…<X[m−1]<X[m]
对于每个 k(2≤k≤m)都存在两个整数 i 和 j (1≤i,j≤k−1,i 和 j 可相等),使得 X[k]=X[i]+X[j]。
你的任务是:给定一个整数 n,找出符合上述条件的长度 m 最小的"加成序列"。如果有多个满足要求的答案,只需要找出任意一个可行解。
输入格式
输入包含多组测试用例。每组测试用例占据一行,包含一个整数 n。当输入为单行的 0 时,表示输入结束。
输出格式
对于每个测试用例,输出一个满足需求的整数序列,数字之间用空格隔开。每个输出占一行。
数据范围
1 ≤ n ≤ 100
输入样例:
5
7
12
15
77
0
输出样例:
1 2 4 5
1 2 4 6 7
1 2 4 8 12
1 2 4 5 10 15
1 2 4 8 9 17 34 68 77
来源:https://www.acwing.com/problem/content/description/172/
2. 思路分析:
分析题目可以知道这是一道求解最优解的问题,我们需要在所有符合要求的构造序列中找到长度最短的序列,因为需要搜索所有的方案所以可以使用dfs来解决,首先我们可以分析一下递归的最大深度,因为n最大是100而且当前数字可以是前面任意两个数的和所以最大递归的深度为100,但是答案最优解的答案深度应该没有那么深,举个例子我们n = 64,那么加成序列为:1,2,4,8,16,32,64,序列总长度为7,对于这种递归深度比较深但是答案在深度比较浅的问题特别适用于迭代加深来解决,迭代加深本质上是一个dfs,只是在dfs时候是每一层每一层来搜索,相当于是划分一个搜索区域,如果当前深度没有搜到答案那么就扩大搜索范围,也即扩大搜索的深度,直到搜索到答案为止,迭代加深可以用来解决分支特别深但是答案在比较浅的分支里面,可以有效减少搜索空间,这样效率会高很多;此外我们还可以加上一些剪枝优化:① 优化搜索顺序,我们可以从大到小枚举下一个数字,依次考虑每一位填什么,可以减少分支的选择数目;② 排除等效冗余,因为可能好几个两位数字相加和都是相等的,但是他们属于同一种情况所以我们只需要搜索其中一种情况即可,可以声明一个st数组来标记对应的数字否使用过(保证当前这一层不搜索重复的元素),如果使用过那么跳过即可,使用path来记录递归过程中的方案。
3. 代码如下:
from typing import List
class Solution:
def dfs(self, u: int, n: int, k: int, path: List[int]):
# 到达当前递归的最深边界
if u == k:
return path[u - 1] == n
# 需要重新声明一个布尔型数组, st列表保证当前层不搜索重复的元素
st = [0] * (n + 10)
# 暴力枚举前当前数字可能的两个数字的和
for i in range(u - 1, -1, -1):
for j in range(i, -1, -1):
# 计算两个数字的和, i可以等于j
s = path[i] + path[j]
if s > n or st[s] == 1 or s <= path[u - 1]: continue
st[s] = 1
path[u] = s
# 不回溯也是没有什么影响的, 递归的时候使用的是上一层的信息对当前这一层没有什么影响, 在下一次递归之前这个位置会被覆盖掉所以没有什么影响
if self.dfs(u + 1, n, k, path): return True
# 搜索完所有分支直接都没有找到答案直接return False
return False
def process(self):
while True:
n = int(input())
if n == 0: break
k = 1
path = [0] * (n + 10)
# path用来记录递归过程中搜到的答案
path[0] = 1
# 当前没有搜到答案那么深度加1, 也即k + 1
while not self.dfs(1, n, k, path):
k += 1
# 输出答案
for i in range(k):
print(path[i], end=" ")
# 换行
print()
if __name__ == "__main__":
Solution().process()