acm 2248
给定n,找最小的序列。
a0 = 1
am = n
a0 < a1 < a2 ... < am
任意 ak = al1 + al2.
l1 l2 可以相等
如 n = 5
找到 1 2 3 5
或者 12 4 5 都可 输出一个即可
但是 12 3 4 5 就不是最短的了
开始问题没想好,总是超时,参考了
http://hi.baidu.com/ecjtuzdh/blog/item/ea5d2c025ba7c381d53f7ce9.html
提到问题的关键
对于1<=P<=k (a[k]=n) 有a[p]=a[p-1]+a[t] ,t为[0,p-1]中的一个数
刚开始其实也有想到,但是总觉得没有完全证明,对于a[k]最后一个数这个结论肯定成立,否则序列可更短,但是对于序列中前面的数呢。
1 , 2, 4, 8, 10
1, 2 , 4 , 8 , 16
问题是 1, 2, 4 , 8 ,10 ,16, 26这个序列要不要被考虑搜索呢?
开始觉得可能需要因为26直接由10,16计算来比较快到达26.
但其实
1 2 4 8 16 24 26是一样的
16 = u + a u = 8 a = 8
10 = u + b u = 8 b = 2
然后 1 2 4 u u+a u + u + a u + u + a +b一样能取到26长度不会更长。
所以证明成功,因为后面的序列如果之和10右关系则1,2 , 8 10可取代,如果只和16
有关系则1,2,4,8,16可以取代,而26的情况前面分析了也可以取代。所以,能剪掉很多枝。
另外可以用归纳法证明
对于到达任意数字的最短路径。
当路径长度为2的时候
a1 a2 (a2 = a1 + a1)
显然成立
假设对于路径长度为n的到任意数字的最短路径成立,
那么对于n+1的最短路径
如果不成立,即对an+1不符合归则
则 a(n+1) = ai + aj; (i < n-1, j < n-1)
则到a(n+1)存在序列 a1, a2 ai aj an+1符合要求
所以与原序列是最短路径矛盾,得证。
这个证明的正确性不是很确定,呵呵,总之是可以剪枝了,不需要考虑那种情况了。
另外搜素的时候从大数字的分支优先搜索速度较快,因为大数字的分支内容较少,且容易较快的到达目的数字。
代码如下
恩,发现用g++速度比c++慢很多0M->16M,恩以后都用c++提交:)
这个题目也可以用BFS求解,16Ms,用open close表记录已经出队列的元素信息即可,需要的空间比较大。
//DFS 算法
#include <iostream> using namespace std; const int MaxNum = 100; int a[MaxNum]; //递归中的当前扫描路径 int b[MaxNum]; //记录当前找到的最短路径 void PrintPath(int n) { for (int i = 0; i <= n; i++) cout << b[i] << " "; cout << endl; } int shortest_len = MaxNum; void FindShortestChian(int n, int i) { for (int j = i - 1; j >= 0; j--) { int sum = a[i - 1] + a[j]; if (sum == n) { a[i] = n; for (int k = 0; k <= i; k++ ) b[k] = a[k]; shortest_len = i; return; } else if (sum < n && i + 1 < shortest_len) { a[i] = sum; FindShortestChian(n, i+1); } } } int main(int argc, char *argv[]) { int n; a[0] = 1; a[1] = 2; b[0] = 1; b[1] = 2; while (1) { cin >> n; if (n == 0) break; else if (n == 1 || n == 2) PrintPath(n - 1); else { shortest_len = MaxNum; FindShortestChian(n, 2); PrintPath(shortest_len); } } return 0; }
//BFS算法
#include <iostream> #include <stack> using namespace std; int Data[9000000]; int PreIndex[9000000]; //close 指向当前要处理的队列元素,close前即为已经出队列的 //open指向队列尾部,即新加入元素要插入的位置 //close == open 表示队列为空了 //data 数组存储队列元素数值 //index 数组存储对应队列元素的前驱元素的下标索引 void PrintPath(int close, int n) { stack<int> s; s.push(n); for (int i = close; i != -1; i = PreIndex[i]) s.push(Data[i]); while (!s.empty()) { cout << s.top() << " "; s.pop(); } cout << endl; } void FindShortestChian(int n) { Data[0] = 1; PreIndex[0] = -1; Data[1] = 2; PreIndex[1] = 0; int close = 1; int open = 2; while (close != open) { int a = close; int sum; while (a != -1){ sum = Data[close] + Data[a]; if (sum == n) { PrintPath(close, n); return; } else if (sum < n) { Data[open] = sum; //入队列 PreIndex[open++] = close; //存前驱索引为当前处理元素index,open++ } a = PreIndex[a]; } close++; //当前元素处理完,出队列 } } int main(int argc, char *argv[]) { int n; while (1) { cin >> n; if (n == 0) break; if (n == 1) cout << "1" << endl; else if (n == 2) cout << "1 2" << endl; else FindShortestChian(n); } return 0; }