限界深搜——埃及分数 两种剪枝
2016-05-20 20:23
453 查看
Description
问题描述:在古埃及,人们使用单位分数的和(形如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, 编程计算最好的表达方式。
Input
输入:a b
Output
输出:若干个数,自小到大排列,依次是单位分数的分母。
Sample Input
19 45
Sample Output
5 6 18
Hint
【数据规模】
0<a<b<1000
题目本身是一个简单的限界深搜,每次枚举分母,每次x/y-1/i,直到当前深度=maxdepth的时候,判断最后剩下的分数,也即最后一个答案是否符合埃及分数的约定。
有两个至关重要的剪枝:
1.保证枚举的单调性——每次从小于x/y的最大分数从大到小开始枚举,以节省枚举时空。
2.基于剪枝1,如果“即使以后的每次都加上当前枚举分数(保证了最大)都<=待减分数”,则这个枚举是无效的,可以忽略。
问题描述:在古埃及,人们使用单位分数的和(形如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, 编程计算最好的表达方式。
Input
输入:a b
Output
输出:若干个数,自小到大排列,依次是单位分数的分母。
Sample Input
19 45
Sample Output
5 6 18
Hint
【数据规模】
0<a<b<1000
题目本身是一个简单的限界深搜,每次枚举分母,每次x/y-1/i,直到当前深度=maxdepth的时候,判断最后剩下的分数,也即最后一个答案是否符合埃及分数的约定。
有两个至关重要的剪枝:
1.保证枚举的单调性——每次从小于x/y的最大分数从大到小开始枚举,以节省枚举时空。
2.基于剪枝1,如果“即使以后的每次都加上当前枚举分数(保证了最大)都<=待减分数”,则这个枚举是无效的,可以忽略。
//涉及gcd,用了long long #include<iostream> #include<cstdio> #include<cstring> #include<iomanip> #include<cmath> using namespace std; typedef long long ll; ll A,B,mn,maxdepth,temp[100005]={0},ans[100005]={0}; ll GCD(ll a,ll b) { if(!b)return a; return GCD(b,a%b); } ll getst(ll x,ll y)//找到比 x/y 小的 最大的 1/i { for(ll i=2;;i++)if(x*i>y)return i; } bool judge(ll lim) { if(ans[0]==0)return 1;//答案数组为空 return(temp[lim]<ans[lim]);//因为保证了递减枚举,则temp[lim]肯定为最小的一个,现在要求最小分数最大,即分母最小。 return 0; } bool DFS(ll depth,ll minn,ll x,ll y) { bool flag=0; if(depth==maxdepth) { if(y%x)return 0;//不符合埃及分数的定义,如2/3,4/5;而1/2,2/4,4/8等都是符合要求的。 temp[depth]=y/x;//注意约分 if(judge(depth)) memcpy(ans,temp,sizeof(temp));//存在更优解,更新答案 return 1; } minn=max(minn,getst(x,y));//取得新下界,注意这里取的是max(分母),以防止漏除枚举。 for(ll i=minn;;i++) { if((maxdepth-depth+1)*y<=x*i)break;//剪枝2,这里把(maxdepth-depth+1)*(1/i)<=x/y 做了变形。 ll newy=y*i; ll newx=x*i-y;//做差 temp[depth]=i;//枚举解放入temp数组 ll gcd=GCD(newx,newy);//取得新分数分子分母最小公因数,以便约分。 if(DFS(depth+1,minn+1,newx/gcd,newy/gcd))flag=1;//minn一定要+1,不然会出现重复枚举 //如果下一层搜索结果可行,则说明当前枚举可行 } return flag; } void work(void) { scanf("%lld%lld",&A,&B); for(maxdepth=1;;maxdepth++) { mn=getst(A,B); memset(temp,0,sizeof(temp)); memset(ans,0,sizeof(ans));//不要忘了清空ans数组,每一个层次都可能存在最优解 if(DFS(0,mn,A,B))break;//要求加数最少,找到一个可行解就退出 } for(ll i=0;i<=maxdepth;i++)printf("%lld ",ans[i]);//不要忘了从0开始输出。 } int main(){ work(); return 0; }
相关文章推荐
- iOS开发-SVN管理代码
- 按两次返回键退出程序
- http://www.cnblogs.com/ycjing/p/5281384.html
- Maven初步搭建 (一)
- 数据库----表设计五大范式所解决的问题
- FragmentActivity和Activity的具体区别在哪里
- Material Design - CollapsingToolbarLayout
- qwt模版例程学习设置相位曲线波形
- app上线前需要提前干的事
- HDU_1698_Just a Hook_线段树区间更新
- 一个值得纪念的日子
- 模拟窗口抖动
- 如何让树莓派显示中文?
- 选择排序法
- Android之ListView自带的布局simple_list_item
- jni里报05-20 11:56:40.106: A/libc(6175): Fatal signal 7 (SIGBUS) at 0x00000000 (code=128)错误-jni数组报错
- DateUtils日期工具类
- jetson tk1 上面如何编译opencv程序
- MySQL入门--AUTO_INCREMENT自增约束
- 【Java基础】关键字