题目大意
给出一个真分数 a/b,要求出几个互不相同的埃及分数(从大到小),使得它们之和为 a/b
(埃及分数意思是分子为1的分数,详见百度百科)
如果有多组解,则分数数量少的优先
如果分数数量一样则分母最大的要尽量小,如果最大的分母同样大,则第二大的分母尽量小,以此类推
为了加大难度,会给出k个不能作为分母的数
(2<=a,b<=876,k<=5 并且 a,b 互质)
首先想的是数论,但是呢
推不出来...
然后发现a,b好像不大
貌似可以搜索
但是呢
不知道上界...
那就迭代加深搜索呗
然后想想怎么剪枝
如果知道 a/b,要怎么求出最小 k 使 1/k < a/b 呢(注意符号)
易知 a/b 为真分数 ,a,b,k均为整数 所以 a<b 所以必定有整数 x,y 使得 x*a+y=b 且y<a 所以可得 int(b/a)=int( (x*a+y) /a )=int(x*a/a)+int(y/a) = x 因为 int(b/a)<=b/a,int(b/a)=x 所以 x+1 > b/a , 所以 int(b/a)+1>b/a,
所以1/(x+1) < a/b 又 1/int(b/a)=1/x >=a/b 所以 x+1就是最小的 k 使得 1/k < a/b 所以 k=int(b/a) + 1 证明完毕
知道了 k 的下界,那还要求 k 的上界
显然每次求的分母肯定要比上一次大(题目要求)
所以如果后面所有的分母都为 k ,加起来还没有当前要求的数大,那就不要再搜下去了,再下去也不会有结果的
然后就可以搜了,至于判断是否可用我用的是set
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<set> using namespace std; const int N=1e5+7; set <long long> p; long long t,A,B,n,mxdep; long long ans[N],w[N]; bool flag; inline long long gcd(long long a,long long b) { return b ? gcd(b,a%b) : a; }//gcd是为了约分 inline bool pd() { for(int i=mxdep;i;i--) if(ans[i]!=w[i]) return ans[i]&&ans[i]<w[i]; return 0; }//判断答案是否更优 inline void dfs(long long dep,long long a,long long b,long long mi) { if(dep==mxdep) { if(b%a||(b<(mi-1)*a+1)||p.count(b/a)) return;//判断合法性 //如果a/b可化为 1/k 的形式并且 1/k 可以选 w[dep]=b/a; if(pd()) return;//判断答案是否更优 memcpy(ans,w,sizeof(w)); flag=1;//更新 return; } mi=max(mi,b/a+1);//求出下界 for(long long i=mi;i;i++) { if( (mxdep-dep+1)*b<=i*a ) return;//上界 if(p.count(i)) continue;//判断合法 w[dep]=i;//记录路径 long long xa=a*i-b,xb=i*b;//通分 long long z=gcd(xa,xb); dfs(dep+1,xa/z,xb/z,i+1);//约分,向下一层 } } int main() { cin>>t; for(long long i=1;i<=t;i++) { flag=0; mxdep=1; cin>>A>>B>>n; long long c; p.clear();//细节 while(n--) scanf("%lld",&c),p.insert(c);//存储不合法的数 while(mxdep++) { memset(ans,0,sizeof(ans)); dfs(1,A,B,B/A+1); if(flag) break; }//迭代加深 printf("Case %lld: %lld/%lld=1/%lld",i,A,B,ans[1]); for(int i=2;i<=mxdep;i++) printf("+1/%lld",ans[i]); printf(" "); } return 0; }