POJ2248 Addition Chains(迭代加深搜索)

4 篇文章 0 订阅

题目链接: poj2248

题目大意

给你一个数字n,你需要构造一个首项为1,末项为n的递增序列,并且这个序列的非首项的数字都能从它前面找到两项之和与之相等,前面的两项可以为同一项,即可重复,并且要让这个序列尽可能短,如果有多解输出其中一种序列解即可。(n<=100)

解题思路

朴素搜索

  1. 我们可以从第二项开始构造。因为已知第一项为1,所以通过前面的已知项生成的后续项一定能保证上述的『 x [ i ] + x [ j ] = x [ k ] x[i] + x[j] = x[k] x[i]+x[j]=x[k]』的条件;
  2. 在生成后续项的同时不断和当前已有序列的最后一项比较即可保证『序列递增』的条件;
  3. 而『末项为n』的条件可以在递归的终点去检查一下

此时,我们发现这种搜索方式没有一个准确的搜索终点,或者按d=1的等差数列去考虑则搜索树有可能100层,如果按朴素的dfs去搜索解,显然效率低下,不可取。

我们大致考虑下二进制序列:『1、2、4、8、16、32、64、128』显然符合我们上面要寻找的序列的部分条件,并且长度较少。我们可以大致判定解序列的长度也不会太长,也就是说,搜索树的规模(层数)可以大幅度减少。

此时,我们考虑bfs去搜索解,显然效率以及序列最短的条件都可以达到。但就结果而言,bfs不利于剪枝优化,并且空间复杂度较高,所以采取这种方式。

迭代加深

它能解决搜索树规模未知或者十分庞大,但解的规模比较小的搜索优化。避免我们在非答案的分支上花太多的时间。大致思想是:人为地给一次深度优先搜索中加入一个层数限制,这样就便于较浅的搜索分支地快速转移,等到这种限制的搜索结果中没有我们的答案再进一步扩展搜索层数。虽然可能会造成前几层重复搜索,但显然这些冗余搜索的时间远小于遍历整个搜索树的时间。 总而言之,这种搜索方式以前几层重复搜索的代价,换取让层数较小的所有子树先得到遍历,并且还不用存子树的状态,节约了空间复杂度。

就理解而言,这是一种基于深度优先广度优先,并且较为折中的搜索方式。

回到题目,我们可以把序列的长度作为搜索树的深度,从 2 − n 2 - n 2n开始迭代,当某个深度能够找到解后就退出直接输出答案。每层的搜索中,我们可以以 O ( d e e p t h 2 ) O(deepth^2) O(deepth2)的时间去枚举下一个计算下一层需要填上的数字,当发现计算到最后一层,末项为n,则答案找到。

优化1,去重。如果『x + y』的结果之前已经算过了,此时还能继续搜索说明这个答案无效,那么显然没必要搜索这个的子树,check,开一个vis标记即可。

优化2,两个数字相加大于n,显然不符题意,check。

优化3,由于序列是递增的,所以我们在计算下一项数字时用较大的先算能使得我们搜索的分支更快地到达n地界限,那么就会减少相加比较小地数字地无用搜索。 比如我们要计算的终点是4,此时有『1、2』的序列,那么我们让「2+2」先于「1+1」计算,能一次就找到答案而不用在搜完「1+1」、「1+2」…后才得到答案。总而言之就是拉大每次搜索的步长,之后在慢慢微调,和倍增的思想很类似。

实现代码

#include <iostream>
#include <cstring>
using namespace std;

int n, arr[105];

bool dfs(int u, int dep) {
	if (u == dep) return arr[u - 1] == n; //到达下界 判断该分支的正确性
	bool vis[105] = {false};
	//保证arr[u]等于arr[i]+arr[j]
	for (int i = u - 1; i >= 0; i--) {
		for (int j = i; j >= 0; j--) {
			int s = arr[i] + arr[j];
			if (s > n || vis[s] || s <= arr[u - 1]) continue;
			arr[u] = s; vis[s] = true;
			if (dfs(u + 1, dep)) return true;
		}
	}
	return false;
}

int main() {

	while (cin >> n, n) {
		arr[0] = 1;
		//迭代加深
		for (int i = 1; i <= n; i++) {
			if (dfs(1, i)) {
				for (int j = 0; j < i; j++) {
					cout << arr[j] <<  " \n"[j == i - 1];
				}
				break;
			}
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小胡同的诗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值