/* 埃及分数: 使用单位分数的和(如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<1000),试编程计算最佳表达式。 思路: 若用宽度优先遍历,宽度没有上界,加数选择无界限。 解决方案:采用迭代加深搜索:从小到大枚举深度上线d,每次只考虑深度不超过d的节点。只要解的深度有限,就能枚举到。 深度上限作用:用来剪枝,分母递增顺序扩展,扩展到第i层时,前i个分数之和为c/d,而第i个分数为1/e,则接下来至少还需要(a/b-c/d)/(1/e)个分数,总和才能达到 a/b。 例如,搜索到19/45=1/5+1/100+...,则后面的分数每个最大为1/101,至少需要(19/45-1/5)/(1/101) = 23项总和才能达到19/45,因此前22次迭代是根本不会考虑这颗 子树的。 扩展到第m层,前m-1个分数为e1,e2,..,em-1,那么em满足: a/b - e1 - e2 - ... - em-1 >= 1/em,确定em的下界 若设定深度上限为d,当前为第m层,还剩d-m层,第m层之后的分母应该大于em a/b-e1-e2-...-em-1-em<(1/em)*(d-m) 确定em的上界 解答树模型: 第0层:根 1 :1/2,1/3,1/4,... 2 :1/2的孩子是1/2+1/3,1/2+1/4,... 1/3的孩子是1/3+1/4,1/3+1/5,... 3 :1/2+1/3的孩子是1/2+1/3+1/4,1/2+1/3+1/5,... 输入: 19 45 输出: 19/45 = 1/5 + 1/6 + 1/18 未解决:代码是别人的 */ /* 关键: 1 采用迭代加深搜索:从小到大枚举深度上线d,每次只考虑深度不超过d的节点。只要解的深度有限,就能枚举到。 */ #include<stdio.h> #include<string.h> #include<stack> using namespace std; stack<int> s; stack<int> sbest; //存放最优解 int flag; //标志当前是否得到可行解 int gcd(int m,int n) //求最大公约数 { if(!n) return m; else return gcd(n,m%n); } void minus(int &a,int &b,int c,int d) //分数相减,结果为a/b { int m,n,k; m = a*d - b*c; n = b*d; k = gcd(m,n);//这个起到的作用是化简a/b,消去最大公约数 a = m/k; b = n/k; } int eq(int a,int b,int c,int d) //判断两个分数是否相等 { int m = a*d - b*c; if(m > 0) return 1; else if( m == 0) return 0; else return -1; } void stackCopy(stack<int>& s1,stack<int> s2) //将s2拷贝到s1中 { stack<int> buf; //中转站buf while(!s1.empty()) //将s1清空 s1.pop(); while(!s2.empty()) { buf.push(s2.top()); s2.pop(); } while(!buf.empty()) { s1.push(buf.top()); buf.pop(); } } void dfs(int a,int b,int start ,int depth) { int n; int c,d; int iEqual2,iEqual3; if(depth == 0) return ; else { for(n = start;;n++) { int iEqual = eq(a,b,1,n); if(iEqual == 0) { s.push(n); if(!flag ||sbest.top() > s.top()) //如果当前没有解或者有更好的解 stackCopy(sbest,s); flag = 1; s.pop(); return ; } else if(iEqual > 0) { if((iEqual3 = eq(a,b,depth,n)) >= 0) // a/b > 1/n * depth ?确保能够更新depth层数 return ; s.push(n);//? 说明走到这里的分支是1/n< a/b < 1/n*depth ,这里可以加n放入进去,找到了一个范围 c=a;d=b; minus(c,d,1,n);//a/b-1/n 消去前面已经加上的 dfs(c,d,n+1,depth-1);//这里由于做了减法,使得a/b减小了,在第一层继续寻找答案 s.pop(); //为什么要弹出 } else continue; } } } int main() { int a,b; stack<int> buf; scanf("%d%d",&a,&b); for(int depth = 1;;depth++) { flag = 0; while(!sbest.empty()) sbest.pop(); dfs(a,b,2,depth); if(flag) break; } while(!sbest.empty()) { buf.push(sbest.top()); sbest.pop(); } while(!buf.empty()) { printf("%d ",buf.top()); buf.pop(); } scanf("%d",&a); return 0; }