题解:P1763 埃及分数
前言
这道题的剪枝真的太酷啦(基本上想不到)!
思路
这道题得用迭代加深搜索(IDDFS)。
有以下几种剪枝。
1.假设
2.求分母的左边界。我们知道
3.求分母的右边界。我们知道
4.如果已经有结果了,那么最大分母为
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL N=1e7;
LL a,b,q[11],p[11];
int dep;
bool flag=false;
LL gcd(LL x,LL y){//最大公约数
return y==0?x:gcd(y,x%y);
}
void dfs(LL a,LL b,int idx){
if(idx>dep) return;//超出所规定的深度,返回
if(a==1){
if(b>p[idx-1]){//判断分母是否大于上一个分母
p[idx]=b;
if(!flag||p[idx]<q[idx]){//如果没有答案 或者 现在最后一个分母比上一个答案的最后一个分母小
for(int i=1;i<=idx;i++) q[i]=p[i];//赋值
flag=true;//已经有答案
}
}
return;
}
LL lf=max(p[idx-1]+1,(a+b-1)/a),rt=min(N,(dep-idx+1)*b/a);//剪枝2、3
if(flag&&rt>=p[dep]) rt=p[dep]-1;//剪枝4
for(LL i=lf;i<=rt;i++){//枚举分母
p[idx]=i;
LL son=a*i-b,mth=b*i;
LL k=gcd(mth,son);
dfs(son/k,mth/k,idx+1);
}
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
scanf("%lld%lld",&a,&b);
LL c=gcd(a,b);
a/=c,b/=c;//先给分数化简
p[0]=1;
for(dep=1;dep<11;dep++){//枚举深度,剪枝1
dfs(a,b,1);
if(flag){//如果有结果,直接输出
for(int i=1;i<=dep;i++) printf("%lld ",q[i]);
return 0;
}
}
return 0;
}
到这里就可以拿
5.我们还可以枚举最大分母的大小,建议
for(dep=1;dep<11;dep++){//枚举深度
for(N=1e5;N<=1e7;N*=10){//枚举分母范围,剪枝5
dfs(a,b,1);
if(flag){//如果有结果,直接输出
for(int i=1;i<=dep;i++) printf("%lld ",q[i]);
return 0;
}
}
}
6.最后一个剪枝就厉害了。当我们搜索到倒数第二个分数时,可以进行特殊处理。此时
设
通过第一条式子可以得出
因为
根据公式可得
最后一步就是要求
AC 代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL a,b,q[11],p[11],N;
int dep;
bool flag=false;
LL gcd(LL x,LL y){//最大公约数
return y==0?x:gcd(y,x%y);
}
void dfs(LL a,LL b,int idx){
if(idx>dep) return;//超出所规定的深度,返回
if(a==1){
if(b>p[idx-1]){//判断分母是否大于上一个分母
p[idx]=b;
if(!flag||p[idx]<q[idx]){//如果没有答案 或者 现在最后一个分母比上一个答案的最后一个分母小
for(int i=1;i<=idx;i++) q[i]=p[i];//赋值
flag=true;//已经有答案
}
}
return;
}
if(idx==dep-1){//剪枝6
LL lf=(b*4/(a*a))+1,rt=min(2*N/a,N*(N-1)/b);
for(LL i=lf;i<=rt;i++){
LL d=a*a*i*i-4*b*i;
LL k=sqrt(d);
if(k*k!=d||((a*i-k)&1)) continue;
p[idx]=(a*i-k)>>1;
p[dep]=(a*i+k)>>1;
if(p[idx]<=p[idx-1]||p[dep]<=p[idx]) continue;
if(!flag||p[dep]<q[dep]){
for(int j=1;j<=dep;j++) q[j]=p[j];
flag=true;
}
}
return;
}
LL lf=max(p[idx-1]+1,(a+b-1)/a),rt=min(N,(dep-idx+1)*b/a);//剪枝2、3
if(flag&&rt>=p[dep]) rt=p[dep]-1;//剪枝4
for(LL i=lf;i<=rt;i++){//枚举分母
p[idx]=i;
LL son=a*i-b,mth=b*i;
LL k=gcd(mth,son);
dfs(son/k,mth/k,idx+1);
}
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
scanf("%lld%lld",&a,&b);
LL c=gcd(a,b);
a/=c,b/=c;//先给分数化简
p[0]=1;
for(dep=1;dep<11;dep++){//枚举深度,剪枝1
for(N=1e5;N<=1e7;N*=10){//枚举分母范围,剪枝5
dfs(a,b,1);
if(flag){//如果有结果,直接输出
for(int i=1;i<=dep;i++) printf("%lld ",q[i]);
return 0;
}
}
}
return 0;
}
结尾
我从来没有做过剪枝这么巧妙的题。这道题花费了我一天的时间,虽然还有其他剪枝没写,但是收获也很大。希望大家多刷刷有挑战的题,长长见识,增长才干,在赛场上取得优异成绩!