http://acm.nefu.edu.cn/JudgeOnline/problemshow.php?problem_id=358
欢迎AC。
题目大意:
对于每一个非负有理数,我们知道它一定能划归成某些特殊真分数之和,特殊真分数要满足它们的分子为1,但是我们知道,对于无穷级数1/2+1/3+1/4…。虽然,它是发散的,但是该级数增长得极为缓慢,例如到了数百万之后,和也在18~19左右。
若干年来,不断有人宣称发现了该级数的特殊性质,这些都对这个问题的研究起到了深远的影响。
你的任务来了,要求给你个真分数,你需要将其化简为最少的若干特殊真分数之和,你要输出这个序列(序列按递增序)。
如果有不同的方案,则分数个数相同的情况下使最大的分母最小。若还相同,则使次大的分母最大……以此类推。
如: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
最好的是最后一种,因为18 比180, 45, 30,都小。
输入:多组数据输入. 每组输入一行,a,b,(0<=a<b<=1000)表示要化简的数为a/b 输出:输出一个算式,格式见样例。每个+、=号两边都没空格。
期初拿到这道题之后的思路是DFS+剪枝,后来发现,剪枝的实现真实太难了,儿深度又如此之大,所以就百思不得其解了。
上网查阅了些资料后我才发现,这道题用了一种叫做迭代加深搜索的搜索方式。
下面本人从“科学哲学”的角度说明本题的全部解题过程。
首先,我们可以看出我们需要的结果是一个分母的k元组,而这k个数是不存在重复的,且是递增的,所以可以确定大方向,一定是DFS,但是深度又不确定。题目中说了从1/2加到1/1000000的值才不过十几,所以可知深度实在是可能很大。所以考虑对深度进行限制,这就是迭代加深搜索,其实本质上可以看成是BFS型的DFS。
这样搜索方式就定了,我们可以对不符合当前搜索深度要求的所有情况进行剪枝,知道找到最优解为止。
接下来进行题目分析:
对于任意给定的a,b,1/k<=a/b<=1/p成立(这比较抽象,不过幸好这不是重点),也就是说,为了使程序具有更高的剪枝效率,我们需要找到所谓的上界与下界。
对于上界的寻找,我们可以极端化考虑。对于任意满足条件的a,b,都有a/b<INF/b,所以我们规定上界为INF/b ,这个想法很单纯,切勿多想。
对于下界的寻找:a/b=1/(b/a),所以如果规定int t=b/a,那么t<=b/a恒成立,所以可以说1/t>=1/(b/a)恒成立。又在递归迭代搜索中,dep表示当前搜索限制深度,k表示当前搜索深度,则当前分数就一定是由(dep-k+1)个特殊真分数的和,每个真分数最大就是1/[(b/a)*(dep-k+1)],所以对原式进行放缩,有:
a/b=1/(b/a)=(dep-k+1)*1/[(b/a)*(dep-k+1)]<=1/t,即a/b>=t,故可知,t为理论下界。
这样上下界都有了,我们可以写出下列代码:
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
#define LL __int64;
const int INF = ~0U>>1;
//无穷大的表示法(~0U的意思是先把0强制转化为unsigned类型,再各位取反),这样得到的就是最大值。
const int N = 1000; //最大所需深度
int dep,flag; //当前深度限制变量 和 成功标记变量
int ans[N],d[N]; //ans为结果数组,d为深度结果数组
//最大公约数算法
int gcd(int a,int b) {return b ?gcd(b,a%b):a;}
//DFS基本主模块
void dfs(int a,int b,int k) //k为当前深度
{
if(k == dep + 1) return; //深度优先出口
if(b % a == 0&& b / a > d[k-1])
//这种情况是找到了解的情况,其实就是a==1的情况,这种情况肯定有解。
{
d[k] = b / a;
if(!flag || d[k] < ans[k])
memcpy(ans,d,sizeof(d));
flag = 1;
return;
}
int s = b / a; //分母的理论下界寻找
if(s <= d[k-1]) s = d[k-1] + 1; //确保下界是正确的
int t = (dep - k + 1) * b /a; //分母的理论上界寻找
if(t > INF / b) t = INF / b; //确保上界是正确的
if(flag && t >= ans[dep]) t = ans[dep] - 1; //如果上界超过前层递归的范围就把上界进行缩短
for(int i=s;i<=t;i++)
{
d[k] = i; //表示k这一层目前所到的最大深度
int m = gcd(i*a - b,b*i); //最大公约数
dfs((i*a-b)/m,b*i/m,k+1); //继续DFS
}
}
void Work(int a,int b)
{
d[0] = 1; //d[0]初始化
flag = 0; //默认找不到解
for(dep=1;dep <= N;dep++) //深度加深
{
dfs(a,b,1); //进行迭代递归搜索
if(flag) //如果找到解的话
{
printf("1/%d",ans[1]); //输出解
for(int i=2;i<=dep;i++)
printf("+1/%d",ans[i]);
cout<<endl;
break; //不继续进行加深搜索
}
}
}
//主函数,不解释。
int main()
{
int a,b;
while(cin>>a>>b)
{
cout<<a<<"/"<<b<<"=";
Work(a,b);
}
return 0;
}
至此,解题报告结束。