“埃及分数”问题浅谈对迭代加深搜索的理解

迭代加深搜索(IDDFS)的思想

迭代加深搜索一般用来求解状态树“非常深”,甚至深度可能趋于无穷,但是“目标状态浅”的问题。如果用普通的DFS去求解,往往效率不够高。此时我们可以对DFS进行一些改进。最直观的一种办法是增加一个搜索的最大深度限制maxd,一般是从1开始。每次搜索都要在maxd深度之内进行,如果没有找到解,就继续增大maxd,直到成功找到解,然后break。

如下图所示,如果用DFS,需要15步才能找到结点3,但是用迭代加深搜索,很快即可找到结点3。

在使用迭代加深搜索时,通常还要引入一个估价函数h(),来预测从当前深度还有至少多少步才能到达目标状态。假设当前在第cur层,当cur+h(cur)>maxd时候,就说明不论怎么走,都不可能在maxd的限制之内找到目标状态,此时就可以进行“剪枝”操作。这样的和带有估价函数的迭代加深搜索就是IDA*算法。为了更好的理解该算法,下面以“埃及分数”这一经典的例子来作为说明。

埃及分数问题

在古埃及,人们使用单位分数的和(即1/a,a是自然数)来表示一切有理数,例如,2/3=1/2+1/6,但是不允许2/3=1/3+1/3,因为在加数中不允许有相同的。对于一个分数a/b,表示的方法有很多种,其中加数少的比加数多的好,如果加数个数相同,那么最小的分数越大越好。例如,19/45=1/5+1/6+1/18是最优方案。

输入两个整数a,b(0<a<b<500),试编程计算最佳表达式。

样例输入:

495 499

样例输出:

Case 1: 495/499=1/2+1/5+1/6+1/8+1/3992+1/14970

问题分析

首先,根据题意可以发现,本题的解答树非常的庞大,不仅深度没有明显的上界,而且在理论上加数的选择也是无限的,即每一层的结点数量也是无限的。这样的话,如果使用普通的BFS,第一层都扩展不完。然而本题要实现两个目的:1.加数的个数尽量少。2.最小的那个分数尽量大,即最大的分母尽量小。

为了实现第一个目标,我们自然想到可以逐一枚举可能的个数,设maxd表示一共有maxd+1个分数相加恰好等于a/b(下标从0开始),按照这样的思路,一定可以找到加数最少的解。

接下来考虑如何实现第二个目标。第二个目标其实是在告诉我们如果存在多解的时候,要更新当前找到的最优解。这里容易犯的一个错误是:找到了解之后立即全部return true。这样的写法只能实现目标1,并没有真正实现目标2。正确的做法应该是找到一组解后返回前一层,继续寻找新的解是否存在。

那么自然会问:既然找到了解不是返回条件,那正确的返回条件是什么?我们继续分析,假设当前已经在第cur层,即0~cur的所有的分母都已经确定了,剩下的分数还需要aa/bb,再假设我们此时需要从分母i>=from的数开始寻找,不难发现,如果(maxd+1-d)*(1/i)≤aa/bb,即剩下的分数全部都为1/i时候,他们的和才刚好等于aa/bb。而实际情况是:分母不允许重复出现,即实际的和肯定小于(maxd+1-d)*(1/i),这个时候如果i继续增加,(maxd+1-d)*(1/i)只会更小于aa/bb,情况更糟糕,因此在此处应该停止枚举。这样,我们就分析出来了正确的返回条件:当出现(maxd+1-d)*(1/i)≤aa/bb时,break。

通过以上分析,我们还确定出来了dfs时候的入口参数: cur表示当前在第cur层,from表示第cur层分母的起点, aa表示剩下的分数的分子,bb表示剩下的分数的分母。即dfs(cur,from,aa,bb)。至此,本题的算法的整体框架已经分析完毕了,我们成功地利用IDA*算法解决了这道经典例题。

代码:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<cctype>
#include<functional>
using namespace std;

#define me(s) memset(s,0,sizeof(s))
#define pb push_back
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
typedef pair <int, int> P;

int maxd;
const int N = 1000;
int ans[N];
int v[N];

int get_first(int x, int y)//找到第一个小于x/y的单位分数的分母
{
	int res = y / x;
	return res*x >= y ? res : res + 1;
}

ll gcd(ll a, ll b)
{
	return b == 0 ? a : gcd(b, a%b);
}

bool better(int d)//更新当前的解
{
	for (int i = d; i >= 0; i--)//由于分母是由小到大存储的,因此逆序枚举
	if (v[i] != ans[i])
		return ans[i] == -1 || v[i]<ans[i];
	return false;
}
bool dfs(int d, int from, ll aa, ll bb)
{
	if (d == maxd)
	{
		if (bb%aa)return false;//如果不能整除,搜索失败
		v[d] = bb / aa;
		if (better(d))memcpy(ans, v, sizeof(ll)*(d + 1));
		return true;
	}
	bool ok = false;
	from = max(from, get_first(aa, bb));//更新from这一步容易忽略,假设第d-1个分数的分母是a,第d个分数的分母不一定要从a+1开始,还要考虑1/(a+1)是否小于等于aa/bb
	for (int i = from;; i++)
	{
		if (bb*(maxd + 1 - d) <= i*aa)break;//此处才是正确的停止枚举的条件
		v[d] = i;//枚举新的分母
		ll b2 = bb*i;//计算aa/bb-1/i的分子,分母
		ll a2 = aa*i - bb;
		ll g = gcd(a2, b2);//计算最大公约数,进行约分
		if (dfs(d + 1, i + 1, a2 / g, b2 / g))ok = true;//注意,不要写成return true,因为找到一组解并不能当做停止枚举的条件
	}
	return ok;
}
int main()
{
	int a, b;
	int rnd = 0;
	while (~scanf("%d%d", &a, &b))
	{
		int ok = 0;
		for (maxd = 1;; maxd++)//迭代加深搜索的主体框架,maxd要设置为全局变量
		{
			memset(ans, -1, sizeof(ans));
			if (dfs(0, get_first(a, b), a, b))
			{
				ok = 1;
				break;
			}
		}
		printf("Case %d: %d/%d=", ++rnd, a, b);
		for (int i = 0; i <= maxd; i++)
		{
			if (i)printf("+");
			printf("1/%d", ans[i]);
		}
		printf("\n");
	}
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-01 16:07:19

“埃及分数”问题浅谈对迭代加深搜索的理解的相关文章

【迭代加深搜索】埃及分数问题

谢谢阿苏~http://blog.csdn.net/urecvbnkuhbh_54245df/article/details/5856756 [迭代加深搜索(ID,iterative deepening)]:从小到大枚举上限maxd,每次执行只考虑深度不超过maxd的结点. ------对于可以用回溯法求解但解答树的深度没有明显上限的题目,可以考虑ID算法: ------优点:它主要是在递归搜索函数的开头判断当前搜索的深度是否大于预定义的最大搜索深度,如果大于,就退出这一层的搜索,如果不大于,就

vijos1308 埃及分数(迭代加深搜索)

题目链接:点击打开链接 题目描述: 在古埃及,人们使用单位分数的和(形如1/a的, a是自然数)表示一切有理数.如:2/3=1/2+1/6,但不允许2/3=1/3+1/3,因为加数中有相同的.对于一个分数a/b,表示方法有很多种,但是哪种最好呢?首先,加数少的比加数多的好,其次,加数个数相同的,最小的分数越大越好. 如:19/45=1/3 + 1/12 + 1/180 19/45=1/3 + 1/15 + 1/45 19/45=1/3 + 1/18 + 1/30, 19/45=1/4 + 1/6

埃及分数问题_迭代加深搜索_C++

一.题目背景 http://codevs.cn/problem/1288/ 给出一个真分数,求用最少的1/a形式的分数表示出这个真分数,在数量相同的情况下保证最小的分数最大,且每个分数不同. 如 19/45=1/3 + 1/12 + 1/180 二.迭代加深搜索 迭代加深搜索可以看做带深度限制的DFS. 首先设置一个搜索深度,然后进行DFS,当目前深度达到限制深度后验证当前方案的合理性,更新答案. 不断调整搜索深度,直到找到最优解. 三.埃及分数具体实现 我们用dep限制搜索层数,先从2开始,每

搜索专题小结:迭代加深搜索

迭代加深搜索 迭代加深搜索(Iterative Deepening Depth-First Search, IDDFS)经常用于理论上解答树深度上没有上界的问题,这类问题通常要求出满足某些条件时的解即可.比如在"埃及分数"问题中要求将一个分数a/b分解成为若干个形如1/d的加数之和,而且加数越少越好,如果加数个数相同,那么最小的分数越大越好.下面总结一下该方法的一般流程: (1)概述:迭代加深搜索是通过限制每次dfs的最大深度进行的搜索.令maxd表示最大的搜索深度,那么dfs就只能在

算法复习——迭代加深搜索(骑士精神bzoj1085)

题目: Description 在一个5×5的棋盘上有12个白色的骑士和12个黑色的骑士, 且有一个空位.在任何时候一个骑士都能按照骑士的走法(它可以走到和它横坐标相差为1,纵坐标相差为2或者横坐标相差为2,纵坐标相差为1的格子)移动到空位上. 给定一个初始的棋盘,怎样才能经过移动变成如下目标棋盘: 为了体现出骑士精神,他们必须以最少的步数完成任务. Input 第一行有一个正整数T(T<=10),表示一共有N组数据.接下来有T个5×5的矩阵,0表示白色骑士,1表示黑色骑士,*表示空位.两组数据

UVA-11214 Guarding the Chessboard (迭代加深搜索)

题目大意:在一个国际象棋盘上放置皇后,使得目标全部被占领,求最少的皇后个数. 题目分析:迭代加深搜索,否则超时. 小技巧:用vis[0][r].vis[1][c].vis[2][r+c].vis[c-r+N]分别标志(r,c)位置相对应的行.列.主.副对角线有没有被占领(详见<入门经典(第2版)>P193),其中N表示任意一个比行数和列数都大(大于等于)的数. 代码如下: # include<iostream> # include<cstdio> # include&l

USACO/fence8 迭代加深搜索+剪枝

题目链接 迭代加深搜索思想. 枚举答案K,考虑到能否切出K个木头,那么我们当然选最小的K个来切. 1.对于原材料,我们是首选最大的还是最小的?显然,首选大的能够更容易切出,也更容易得到答案. 2.对于目标木头,我们是优先得到最大的还是最小的?显然,由于K个木头我们都要得到,那么当然先把最大的(最难得到的)先得到,这种搜索策略更优. 3.假设总原材料为all,前K个木头总和为sum,那么all-sum就是这一次切割过程中能[浪费]的最大数目.对于一个切剩下的原材料,若它比最小的目标木头还要小,则它

hdu 1560 DNA sequence(迭代加深搜索)

DNA sequence Time Limit : 15000/5000ms (Java/Other)   Memory Limit : 32768/32768K (Java/Other) Total Submission(s) : 15   Accepted Submission(s) : 7 Font: Times New Roman | Verdana | Georgia Font Size: ← → Problem Description The twenty-first century

hdu 1560 迭代加深搜索

链接:http://acm.hdu.edu.cn/showproblem.php?pid=1560 只能说bin神太给力了.. 又学到不少新知识.. 迭代加深搜索,貌似 又叫IDA*, 就是给搜索深度一个限制,搜索到一个满足条件就结束. 注意剪枝~ 代码: #include <iostream> #include <cstdio> #include <cstring> using namespace std; char g[10][10]; int size[10];