迭代加深搜索算法(iterative deepening depth-first search (IDS or IDDFS)) 可以被视作是广度优先搜索算法(BFS)和深度优先搜索算法(DFS)的结合。常用于解决在广度很深度上都无限的问题,最经典的便是埃及分数:


在古埃及,人们使用单位分数的和(形如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 + 1/180
19/45=1/5 + 1/6 + 1/18.
最好的是最后一种,因为1/18比1/180,1/45,1/30,1/180都大。
给出a,b( 0 < a < b < 1000),编程计算最好的表达方式。
Input:
a b
Output:
一个等式

Sample Input:
3 4
Sample Output:
3/4 = 1/2 + 1/4


埃及分数问题的广度(单位分数的大小)和深度(单位分数的求和数量)都是无限的。在单纯的DFS问题中,很多时候剪枝技术只是一种优化,但在IDDFS中,剪枝直接限定了合理有效的边界,使搜索在有限的范围内进行,使得回溯和搜索都成为了可能。因此在IDDFS中,如何剪枝成为了不可绕过的重要话题,直接决定了是否能合理地解决问题。

简要回顾以下IDDFS的思想:逐渐增加DFS的递归深度,同时每一层都采用BFS进行搜索,可以说IDDFS就是用DFS串起来的BFS,又或者用笔者在DFS笔记中的类比,DFS是”动态循环“,普通的DFS的循环体只是一个简单判断,而IDDFS中的循环体就是一个BFS。

本题存在两个维度的最优解情况:1、和式中的分数最少,2、和式中的最小的分数最大(分母最小),其中2是在1的条件下进行判断。考虑到IDDFS中DFS和BFS的从属关系,自然1应当由DFS处理,而2则是BFS。

接下来具体分析一下题目,首先本题采用分数作为结果,为了保证过程没有误差,采用浮点型进行计算是错误且复杂的,会牵扯到小数和分数的转换问题。在本题中,对于分数而言分子和分母成对出现,不如用pair<int,int>来储存它们,first是分子,second是分母。在此基础上,需要重新定义这种特殊类型的分数的求和函数:

int gcd(int a,int b)     //辗转相除法求公约数
{return b==0 ? a : gcd(b,a%b);}
pair<int,int> sum(pair<int,int> a, pair<int,int> b){pair<int,int> result;int c = a.first*b.second+b.first*a.second;int d = a.second*b.second;int g = abs(gcd(d,c));c /= g; d /= g;result.first = c; result.second = d;return result;
}

有了求和方法之后似乎分数运算变得可行,如果要比大小的话只需要将其中一个的first*(-1)传入,再比较结果的符号即可。同时本题需要输出一个和式,这个和式再DFS的过程中也需要被跟踪,所以不妨先放出两个字符串和整型互转函数:

string ItS(int num){string str;stringstream ss;ss<<num;ss>>str;return str;
}
int StI(string str){int num;stringstream ss;ss<<str;ss>>num;return num;
}

准备好基本工具后我们来理一下解题思路:先试2个分数的和,再试3个分数的和,再...直到在第n个分数的和找到了我们想要的结果a/b,便解决了最优解条件1,再在这一层中继续寻找别的可能解且不再深入,最后根据最优解条件2排序,输出最优解。

从深度上说,IDDFS中DFS的部分往往需要两个基本参数(now和deep),now是当前的递归深度,deep是最深的递归深度,一旦now=deep则返回,deep+1后再继续,deep则有主函数给出。所以IDDFS的递归思路是从0-1,再0-2,再0-3...直到解决问题。

埃及分数是恒有解问题,这一点在题目中也有暗示(没有impossible的情况),且大部分IDDFS的问题在深度上的无限正体现在这一点上。因此我们不用担心由于深度无限而无法跳出,我们总会找到合适的解,并且事实上它不会在深度上花费太多。

而在广度上,我们需要不断地求和,并记录这些和的结果,运用到下一层的BFS中去,为了简单说明这个问题的思路,我们将算法中位于+号左边的称为“头”,位于+号右边的称为“尾”,作为暴力算法,遍历求和的思路便是将头作为外循环,尾作为内循环,储存求和结果,下面用一个代码解释这个思路:

for(int head=0;head<10;head++){for(int tail = head+1;tail<10;tail++){int sum = head + tail;}
}

在IDDFS中,这个head来自于上一层的BFS中,而这个tail正是当前层的BFS给出的遍历,而sum则是当前层BFS的搜索用元。DFS则在其中担任传递head的作用,并且决定传递几层。

了解了BFS和DFS再IDDFS中分别担任的角色和它们互相作用的原理关系,接下来我们需要解决能使整个解决过程变得可行,变得不再无穷的核心问题——如何剪枝。

事实上在这里称剪枝或许并不准确,毕竟剪枝是DFS中用于避免无效递归的手段,而在IDDFS中,实际上我们更关注给BFS进行“划界”,使得BFS在一个合理的范围内搜索,剩余的无穷的无效范围是不必要的。

在本题中的“划界”方法是:用递增顺序枚举分数,如果接下来的递归层数(deep-now)全加上目前这个分数(1/n)仍然小于目标值(a/b),说明剩下的分数枚举是无效的,可以直接结束枚举,划出右界,同时如果当前分数比目标分数大,则同样不需要枚举,划出左界。给出检测函数:

int check(pair<int,int>now, int after, int deep){//after是小于当前枚举分数的最大分数的分母,pair<int,int> p;                            //deep为递归深度,同时也是求和宽度 pair<int,int>temp;p.first = deep;   p.second = after;temp.first = a*(-1);     temp.second = b;if(sum(now,temp).first>0)       return 2;        //左界以左p = sum(now,p);p = sum(p,temp);return(p.first>=0);        //右界以右
}

之所以check函数不用bool型而是用int型,是因为需要区分左界以左,右界以右和在范围内三种情况,在住主函数中需要另外处理它们,保证主函数给出的head是左界,使IDDFS效率更高。

有了IDDFS的基础模块check划界后,我们继续解决题目中的具体问题。

首先是记录每个head的加合路径,这个路径将再最后被调用输出,简单写一个字符串函数,同时用map<pair<int,int>,stirng>来储存它们:

string road(string pre, pair<int,int> next){string str;str = pre + " + " + ItS(next.first) + "/" + ItS(next.second);return str;
}

另外,在BFS中tail的左值取决于head的road中的最后一个分数,并且如果最后目标值有多解,我们还需要比较它们最后一个分数的大小,所以需要一个提取最后一个分数分母的函数:

int GetStringTail(string str){       //找到分数加和式中的最后一个分母 int num;string st;for(string::reverse_iterator it=str.rbegin();it!=str.rend();it++){if((*it)=='/')    break;          st+=(*it);        }reverse(st.begin(),st.end());stringstream ss;ss<<st;ss>>num;return num;
}

最后还有一些细节:在读入目标分数后,首先进行化简,不然分母是错误的。如果目标分数的分子是1(已经是埃及分数),避免麻烦,直接输出即可。

整合它们!

#include<bits/stdc++.h>
using namespace std;
int a, b;
bool findthedeep;
map<pair<int,int>,string> m;
vector<string> result;
int gcd(int a,int b)        //辗转相除法求公约数
{ return b==0 ? a : gcd(b,a%b);}
pair<int,int> sum(pair<int,int> a, pair<int,int> b){pair<int,int> result;int c = a.first*b.second+b.first*a.second;int d = a.second*b.second;int g = abs(gcd(d,c));c /= g; d /= g;result.first = c; result.second = d;return result;
}
string ItS(int num){string str;stringstream ss;ss<<num;ss>>str;return str;
}
int StI(string str){int num;stringstream ss;ss<<str;ss>>num;return num;
}
int check(pair<int,int>now, int after, int deep){     //after是小于当前枚举分数的最大分数的分母,pair<int,int> p;                                  //deep为递归深度,同时也是求和宽度 pair<int,int>temp;p.first = deep;   p.second = after;temp.first = a*(-1);     temp.second = b;if(sum(now,temp).first>0)       return 2;p = sum(now,p);p = sum(p,temp);return(p.first>=0);
}
string road(string pre, pair<int,int> next){string str;str = pre + " + " + ItS(next.first) + "/" + ItS(next.second);return str;
}
int GetStringTail(string str){      //找到分数加和式中的最后一个分母 int num;string st;for(string::reverse_iterator it=str.rbegin();it!=str.rend();it++){if((*it)=='/')    break;          st+=(*it);        }reverse(st.begin(),st.end());stringstream ss;ss<<st;ss>>num;return num;
}
void IDDFS(pair<int,int> top, int now, int deep){ if(now == deep)       return;queue<pair<int,int>> q;  int k = 0;  int s = GetStringTail(m[top]);for(int i=s+1;;i++){if(check(top,i,deep-now) != 1) break;pair<int,int> num;num.first = 1;   num.second = i;q.push(num);k++;}pair<int,int> head, next;while(k--){head = q.front();q.pop();next = sum(top,head);q.push(next);m[next] = road(m[top],head);if(next.first == a && next.second == b){result.push_back(m[next]);findthedeep = 1;}}while(!q.empty()){IDDFS(q.front(),now+1,deep);q.pop();}return;
}
int main(){cin>>a>>b;cout<<a<<"/"<<b<<"=";int c = gcd(a,b);a /= c;   b /= c;            //化简 if(a==1){cout<<a<<"/"<<b;return 0;}queue<pair<int,int>> q;pair<int,int> top;for(int i=2;;i++){for(int t=2;;t++){top.first = 1;  top.second = t;if(check(top,top.second+1,i) == 0)   break;if(check(top,top.second+1,i) == 2) continue;q.push(top);}while(!q.empty()){m.clear();top = q.front();q.pop();m[top] = "1/" + ItS(top.second); IDDFS(top,1,i); }if(findthedeep)    break;}int max = 0;string res;for(int i=0;i<result.size();i++){int num = GetStringTail(result[i]);if(max>num || max==0){max = num;res = result[i];}}cout<<res<<endl;return 0;
}

最后的补充:在IDDFS中,实际上是对DFS和BFS的配合使用,主要分为以下3个要点:

1、BFS:依然是找到核心队列,注意结合DFS的递归深度划分左右界。

2、DFS:依然是找到核心递归,注意逐步加深递归深度。

3、DFS给BFS传递枚举head,BFS负责找到最优解。

4、IDDFS的核心是变化深度上限的DFS,BFS很多时候是被忽视甚至是可以代替的。

5、IDDFS要注意逐渐加深的深度的数据意义,能够帮助快速架构算法逻辑。

IDDFS学习笔记-埃及分数相关推荐

  1. 【初阶】unity3d官方案例_太空射击SpacingShooter 学习笔记 显示分数时,如何让函数之间相互交流...

    [初阶]unity3d官方案例_太空射击SpacingShooter 学习笔记 显示分数时,如何让函数之间相互交流 一.关于 显示分数时,如何让函数之间相互交流 这是一个非常好的逻辑问题 1 思路:主 ...

  2. Python学习笔记:创建分数类

    Python学习笔记:创建分数类 1.编写创建分数类.py # 创建分数类from math import gcd# 定义分数类 class Fraction: def __init__(self, ...

  3. 【近万字】分数傅里叶变换课程学习笔记

    学习自"课堂在线"平台,北京理工大学陶然教授的课程视频,讲解的非常详细全面,数学公式推导都有,以下为学习笔记,仅记录要点部分. 注:学习此课程,按重要程度排序,需要有信号与系统.数 ...

  4. 基于Duffing系统的分数阶混沌研究【基于matlab的动力学模型学习笔记_5】

    /*仅当作学习笔记,若有纰漏欢迎友好交流指正,此外若能提供一点帮助将会十分荣幸*/ 前面的几篇博文我们提到提到的都是整数阶模型,这里我们将对分数阶模型进行一个简单的研究. 摘要:与整数阶混沌相比,分数 ...

  5. IDA*学习笔记-HDU1667 The Rotation Game

    本文将以HDU1667为例,记录笔者学习IDA*的心路历程与一些浅见.本文将着重与上一篇IDDFS的笔记进行联系,比较IDA*与IDDFS的特征区别,同时修正一些对于迭代加深算法的见解. 首先上题: ...

  6. 【学习笔记】线性代数全家桶(在编程竞赛中的应用)

    整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 目录 0x00. 矩阵 0x01. 矩阵 0x02. 矩阵的加法与数量乘法 0x03. 矩阵乘法 0x ...

  7. B站《一天学会 MySQL 数据库》学习笔记

    B站<一天学会 MySQL 数据库>学习笔记 老司机带我飞 职场十字诀:思考.计划.行动.总结.反思 ​关注他 4 人赞同了该文章 登录和退出MySQL服务器 # 登录MySQL $ my ...

  8. mysql select语句详解_mysql学习笔记之完整的select语句用法实例详解

    本文实例讲述了mysql学习笔记之完整的select语句用法.分享给大家供大家参考,具体如下: 本文内容: 完整语法 去重选项 字段别名 数据源 where group by having order ...

  9. BZOJ 2038: [2009国家集训队]小Z的袜子(hose)【莫队算法裸题学习笔记】

    2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 Sec  Memory Limit: 259 MB Submit: 9894  Solved: 4561 [Su ...

最新文章

  1. python 数字循环
  2. 单相计量芯片RN8209D使用经验分享(转)
  3. cshrc设置 ic618_.cshrc一般在什么地方
  4. Java servlet项目里的web.xml
  5. php 字符串 字典序序排序,C++ 怎么实现字典序排序法,自然排序
  6. pythonopencv算法_python opencv之SURF算法示例
  7. 【Python CheckiO 题解】Speech Module
  8. 标准的Java编码规范手册
  9. Python模块的下载与导入
  10. 计算机软硬件逻辑等价性是指,南航计算机组成原理复习ppt.ppt
  11. 扫二维码登陆微信 统计微信男女比例并绘图
  12. 西南石油大学计科院主页
  13. Python---按字典序输出集合的所有非空子集
  14. 用户堆栈和系统堆栈的区别
  15. hdu4745(最长回文子序列)
  16. 23个经过时间考验的应用程序,可以管理您的远程软件开发团队
  17. PriceFromImage\UnCodebase
  18. 英语口语练习三十五之To tell the truth... 老实说……
  19. CrtmpServer不支持FFMpeg截图以及个别客户端推流(如XSplit)的解决办法
  20. 江苏大学计算机导论试题,大学计算机导论

热门文章

  1. Ubuntu 16.04 LTS安装 TeamViewer11
  2. 教育已成中国人不折不扣的国耻
  3. FFmpeg在Linux下搭建 ***
  4. siki学院案例---贪吃蛇 学习总结
  5. 如何用vba在单元格右键快捷菜单中添加自定义的菜单命令
  6. 基于OpenCV的驾驶员疲劳鉴别系统
  7. Office for Mac 2021 正式版发布,版本 16.53
  8. 三万字,100题!Linux知识汇总!​
  9. Python学习小组课程-课程大纲与Python开发环境安装
  10. AXI总线之AXI FULL总线分析与实现