​题目传送门​

一、题目分析

1、迭代加深

看完题目第一反应求最短路:\(bfs\);看了一眼\(n=10000\),肯定会爆空间。

因此考虑\(dfs\),用\(dfs\)求最短路径,那不就是暗示我们使用迭代加深嘛~

2、剪枝

不难看出需要的序列是单调递增的,枚举\(i\),\(j\)得到\(t\);因此如果发现\(ans[i]+ans[j]>n\) 或者 \(ans[i]+ans[j] <= a[k-1]\),那都不需要\(k\)位出手。

强剪枝:\(ans[i]<=ans[i-1]*2\),也就是说后一项最多是前一项的\(2\)倍,如果当前已知\(ans[k]\)然后迭代加深的最大深度为\(pos\),则后面最多还有\(pos-k\)项,也就是说\(ans[k]*2^k<n\)则在\(pos\)次之内一定找不到答案。

二、实现代码

#include<bits/stdc++.h>

using namespace std;

const int N = 10010;

int n; //输入的加法链最后一个数字
int ans[N]; //结果数组
int depth; //迭代加深的最后一个索引位置,不要理解为深度层,比如 a[0] a[1] a[2] ,那么depth=2,按深度说是3

/**
* 功能:迭代加深,在当前的depth深度下,尝试找出答案
* @param k 第几个数字
* @return
*/
bool dfs(int k) {
//如果走过规定好的步骤,回头看一眼,如果最后一次放入的是n,表示成功找到,否则就是失败
if (k > depth)return ans[k - 1] == n;

//最优化剪枝:后面每一项最多是前一项的2倍
//肯定无法到达的,提前干掉,可以极大提高性能!
//这个优化太棒了!
if (ans[k - 1] * (1 << (depth - k + 1)) < n)return false;

//枚举k之前的所有数字,两两一组
for (int i = 0; i < k; i++)
for (int j = i; j < k; j++) { //双重循环保证了可以遍历出两两的所有组合情况
int t = ans[i] + ans[j]; //两两之和
if (t > ans[k - 1] && t <= n) {
ans[k] = t; //将和加入到目标数组中,完成本位置的填充
//尝试进行下一个位置的填充
if (dfs(k + 1))return true; //如果找到答案,就终止;否则需要继续探索之
}
}
return false;
}

int main() {
//优化输入
ios::sync_with_stdio(false);
//加法链第一个数字是1
ans[0] = 1;
//读取多组数据
while (cin >> n, n) {
//遍历每个可能的到达的索引号,在找到第一个解后退出
for (depth = 0;; depth++) {
//如果在当前索引号下就能成功符合要求,表示找到了最短路径,退出就可以了
if (dfs(1)) { //a[0]值是1,从a[1]开始尝试
//找到答案输出
for (int i = 0; i <= depth; i++) {
//结果
printf("%d", ans[i]);
//万恶的UVA,每一行最后如果带空格还WA,还需要判断一下才行
if (i < depth)printf(" ");
}
//每组数完事换行
printf("\n");
//找到就结束,迭代加深的套路思想噢~
break;
}
}
}
return 0;
}