pku acm 2248 addtion chians 解题报告

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;
}

 

 

 

posted @ 2009-06-22 12:52  阁子  阅读(546)  评论(0编辑  收藏  举报