【题意】
把a/b写成不同分数之和,且分数分子必须为1,要求项数尽量小,在此前提下最小的分数尽量大,然后第
二小的分数尽量大……另外有k(0<=k<=5)个不超过1000的正整数不能用作分母。
输入保证2<=a<b<=876,gcd(a,b)=1。
【分析】
通过之前看的简单的埃及分数,知道了大体的求解步骤。
1. 此题需要用迭代加深搜索,如果只dfs,是一个没有下界的搜索,死递归;如果bfs,内存开销大。
2. 选当前分数时,从最接近的值开始,a/b最接近的值就是1/(b/a),然后是结束,结束最大为剩余步数
step*b/a,因为1/(b/a)平均拆成step份,step个分数相加才等于a/b,因为越往后分母越大,如果当前分母都
比step*b/a大,无论后面是多大的分数加和都小于a/b。
3. 将不能出现的正整数用数组标记下来,遍历时进行判断。因为给你不能出现的正整数范围比较小,所以
用数组标记节约时间,如果数据范围大,可以选择用set容器进行存储,进行判断是否当前正整数能使用。使用
数组标记就要注意一个细节,就是正整数不超过1000,所以判断时,先判断是否超过1000,在判断数组,不然
会数组越界。
4. 因为分母会很大,int是存不下的,需要用long long。简单的埃及分数是使用的int。
5. 拆分后的分数项数,没有具体要求所以,最好定大一些,不过此题不用太大,反正10项可以过。
【代码】
#include<stdio.h> #include<string.h> const int N=10; typedef long long LL; LL dep,flag,pre[N],now[N]; bool book[1010]; // 函数功能:求最大公约数 LL gcd(LL a,LL b) { LL temp; while(a!=0) { temp=a; a=b%a; b=temp; } return b; } // 函数功能:遍历第dep步的所有解 void dfs(LL a,LL b,LL k) { if(b%a==0&&b/a>now[k-1]&&(b/a>1000||!book[b/a])) // 找到符合要求的结果 { /* 不要忘记判断最后的结果是否能使用,不然会WA,且要记得b/a的范围在1000以内才能判断,不然会数组越界 */ /* 不能把book放下面判断,没有循环continue不能用,return会出错,可能没有到达dep步b%a==0,但是b/a是不能使用的 */ now[k]=b/a; bool ans=0; for(int i=k;i>=1;i--) { if(now[i]<pre[i]) { ans=1; break; } else if(now[i]>pre[i]) break; } if(!flag||ans) memcpy(pre,now,sizeof(now)); flag=1; return ; } LL s=b/a; if(s<=now[k-1]) s=now[k-1]+1; LL t=(dep-k+1)*b/a; // 迭代搜索执行到第dep步就结束了,限制上界 /* 之所以是这个公式是,s是使等式成立最接近的解,把s平均拆分成dep-k+1份,如果没t还小,剩下的dep-k步无论取多少都会偏小 */ if(flag&&t>pre[dep]) t=pre[dep]-1; for(LL i=s;i<=t;i++) { if(i<=1000&&book[i]) // 判断这个点能否使用,不要忘记范围,不要越界访问 continue; now[k]=i; LL m=gcd(a*i-b,b*i); dfs((a*i-b)/m,(b*i)/m,k+1); } return ; } // 函数作用:简洁。可去掉,放在main函数中 void slove(LL a,LL b) { now[0]=1; for(dep=2;dep<=N;dep++) { dfs(a,b,1); if(flag) { printf("1/%lld",pre[1]); for(LL i=2;i<=dep;i++) printf("+1/%lld",pre[i]); printf("\n"); return ; } } return ; } int main() { int T,cnt=1; scanf("%d",&T); while(T--) { flag=0; memset(book,false,sizeof(book)); LL a,b,k,x; scanf("%lld %lld %lld",&a,&b,&k); while(k--) { scanf("%lld",&x); book[x]=true; } printf("Case %d: %lld/%lld=",cnt++,a,b); slove(a,b); } return 0; }
【收获】
1. 认真思考题目的上界是什么。在题目没有给上界或下界时,需要自己去推出
上界(下界),如果推不出来就尽量开大一些。如果没有上界,递归就是死递归。
2. 注意细节。使用数组时,要注意思考下标是否会越界,此题RE了n次。
3. 思考问题,要认真思考。此题结束条件是一个坑,因为有不能使用的正整
数,所以可能没有走到对应的步数或者走到对应步数的正整数不能使用,如果处理
不好就会WA。认真思考结束条件和特殊数据。
4. long long 的使用。
5. 求最大公约数(好久没做过,忘个差不多了)。