IDDFS学习笔记-埃及分数
迭代加深搜索算法(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学习笔记-埃及分数相关推荐
- 【初阶】unity3d官方案例_太空射击SpacingShooter 学习笔记 显示分数时,如何让函数之间相互交流...
[初阶]unity3d官方案例_太空射击SpacingShooter 学习笔记 显示分数时,如何让函数之间相互交流 一.关于 显示分数时,如何让函数之间相互交流 这是一个非常好的逻辑问题 1 思路:主 ...
- Python学习笔记:创建分数类
Python学习笔记:创建分数类 1.编写创建分数类.py # 创建分数类from math import gcd# 定义分数类 class Fraction: def __init__(self, ...
- 【近万字】分数傅里叶变换课程学习笔记
学习自"课堂在线"平台,北京理工大学陶然教授的课程视频,讲解的非常详细全面,数学公式推导都有,以下为学习笔记,仅记录要点部分. 注:学习此课程,按重要程度排序,需要有信号与系统.数 ...
- 基于Duffing系统的分数阶混沌研究【基于matlab的动力学模型学习笔记_5】
/*仅当作学习笔记,若有纰漏欢迎友好交流指正,此外若能提供一点帮助将会十分荣幸*/ 前面的几篇博文我们提到提到的都是整数阶模型,这里我们将对分数阶模型进行一个简单的研究. 摘要:与整数阶混沌相比,分数 ...
- IDA*学习笔记-HDU1667 The Rotation Game
本文将以HDU1667为例,记录笔者学习IDA*的心路历程与一些浅见.本文将着重与上一篇IDDFS的笔记进行联系,比较IDA*与IDDFS的特征区别,同时修正一些对于迭代加深算法的见解. 首先上题: ...
- 【学习笔记】线性代数全家桶(在编程竞赛中的应用)
整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 目录 0x00. 矩阵 0x01. 矩阵 0x02. 矩阵的加法与数量乘法 0x03. 矩阵乘法 0x ...
- B站《一天学会 MySQL 数据库》学习笔记
B站<一天学会 MySQL 数据库>学习笔记 老司机带我飞 职场十字诀:思考.计划.行动.总结.反思 关注他 4 人赞同了该文章 登录和退出MySQL服务器 # 登录MySQL $ my ...
- mysql select语句详解_mysql学习笔记之完整的select语句用法实例详解
本文实例讲述了mysql学习笔记之完整的select语句用法.分享给大家供大家参考,具体如下: 本文内容: 完整语法 去重选项 字段别名 数据源 where group by having order ...
- BZOJ 2038: [2009国家集训队]小Z的袜子(hose)【莫队算法裸题学习笔记】
2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 Sec Memory Limit: 259 MB Submit: 9894 Solved: 4561 [Su ...
最新文章
- python 数字循环
- 单相计量芯片RN8209D使用经验分享(转)
- cshrc设置 ic618_.cshrc一般在什么地方
- Java servlet项目里的web.xml
- php 字符串 字典序序排序,C++ 怎么实现字典序排序法,自然排序
- pythonopencv算法_python opencv之SURF算法示例
- 【Python CheckiO 题解】Speech Module
- 标准的Java编码规范手册
- Python模块的下载与导入
- 计算机软硬件逻辑等价性是指,南航计算机组成原理复习ppt.ppt
- 扫二维码登陆微信 统计微信男女比例并绘图
- 西南石油大学计科院主页
- Python---按字典序输出集合的所有非空子集
- 用户堆栈和系统堆栈的区别
- hdu4745(最长回文子序列)
- 23个经过时间考验的应用程序,可以管理您的远程软件开发团队
- PriceFromImage\UnCodebase
- 英语口语练习三十五之To tell the truth... 老实说……
- CrtmpServer不支持FFMpeg截图以及个别客户端推流(如XSplit)的解决办法
- 江苏大学计算机导论试题,大学计算机导论