P1763 埃及分数 题解
【-1】前言
这题的剪枝真的太妙了,很难想象巨佬是怎么独立想出来这所有的剪枝的。
本题解没有包含所有的剪枝,只选了我认为最好理解的几条剪枝。
想学习所有的剪枝的右转巨佬的题解。
【1】本题大框架:迭代加深搜索(IDDFS)
看到
那么怎么搜?因为我们不能确定最少分数的个数(这是我们首要要求的),可以考虑枚举分数的个数再进行搜索。
这相当于每次限制了搜索树的深度,当确定一个深度发现了解,深度就不会再加深。
这种每次限制了深度的搜索就叫迭代加深搜索(IDDFS)(又称 IDS)。
我们明显感觉到,每次限制深度为
所以这种搜索有什么优势呢?
如果我们不限定深度,那么我们可能花大力气搜出了一个答案但远没达到最优解(分数的个数超过正解),此时再及时回溯已经很难了(搜索树深度增加一层增加的节点数是指数级的),浪费的时间会更多。
const int N = 11,INF = 1e7;//N为层数上限,INF为分母最大值
void dfs(int a,int b,int x){//(a/b)表示剩余分数大小,x表示当前搜索深度
if(x > dep)//超过了限定深度,退出
return;
if(a == 1){//找到了解(此时x一定等于限定的深度dep,否则在dep更小的时候就会找到解)
if(b > tmp[x - 1]){//如果当前的分母比上
st[x] = b;
if(!flag || tmp[x] < ans[x])//找到了更优解
for(int i = 1;i <= dep;i++)
ans[i] = tmp[i];
}
flag = 1;//标记为已经找到了答案
return;
}
int l = max((b + a - 1) / a,tmp[x - 1] + 1),r = min((dep - x + 1) * b / a,INF);//下一个分数分母的上下界
if(flag && r >= ans[dep])
r = ans[dep] - 1;
for(int i = l;i <= r;i++){
tmp[x] = i;
//请自行模拟分数加减法
int A = a * i - b,B = b * i;
int gcd = GCD(A,B);
dfs(A / gcd,B / gcd,x + 1);
}
}
signed main(){
a = read();b = read();
c = GCD(a,b);
a /= c;b /= c;
tmp[0] = 1;
for(dep = 1;dep <= N - 1;dep++){//枚举深度
dfs(a,b,1);
if(flag){找到了答案
for(int i = 1;i <= dep;i++)
printf("%lld ",ans[i]);
return 0;
}
}
return 0;
}
【2】初步剪枝
其实在上方的代码中已经给出了。
就是这一段:
int l = max((b + a - 1) / a/*(b + a - 1) / a = ceil(b / a)*/,tmp[x - 1] + 1);
int r = min((dep - x + 1) * b / a,INF);//下一个分数分母的上下界
if(flag && r >= ans[dep])
r = ans[dep] - 1;
考虑上下界剪枝。
先考虑下界
上界
这里还有个小优化:当发现已经找到了解,那么如果当前的分数小于当前找到的最优解中最小的分数,那么接下来搜索到的解不会更优。
【3】神奇的剪枝
加了上下界剪枝依然无法通过本题,因为本题有一个很强的数据:
570 877
所以还要继续剪枝。
【3.1】神奇的剪枝 1:
我们发现,限制深度可以大大优化搜索,那么是否可以每次限制一些其它参数,进一步优化搜索呢?
题目还要求最小的分数越大越好,即最小的分数分母越小越好,考虑每次限制分母的最大值
我的枚举是这样的:
限制深度为
如果每次将
所以,我们每次将
因为题目首要满足的是分数个数尽可能小,所以枚举深度的循环在外层。
for(dep = 1;dep <= N - 1;dep++){//枚举深度
ans[dep] = tmp[dep] = INF + 1;
for(max_a = 100000;max_a <= INF;max_a *= 10){//即原文的A
dfs(a,b,1);
if(flag){//找到了答案
for(int i = 1;i <= dep;i++)
cout << ans[i] << ' ';
return 0;
}
}
}
【3.2】神奇的剪枝 2:
这是本题最难想到但也是最妙的地方。
每次层数最深的地方搜索树的节点个数巨大,考虑对最深的两层优化。
根据题意,在
消元,得
这是一个关于
因为是一元二次方程,所以有
继续解方程,得
这还没完,将
综上,
if(x == dep - 1){
const int l = ((b << 2) / (a * a)) + 1,r = min(((max_a << 1)) / a,max_a * (max_a - 1) / b);
for(int i = l;i <= r;i++){
int delta = a * a * i * i - ((b * i) << 2);
int Sqrt = sqrt(delta);
if(Sqrt * Sqrt != delta/*delta不为完全平方数*/ || ((a * i - Sqrt) & 1))
continue;
tmp[x] = (a * i - Sqrt) >> 1;tmp[x + 1] = (a * i + Sqrt) >> 1;
if(!flag || tmp[dep] < ans[dep]){
for(int i = 1;i <= dep;i++)
ans[i] = tmp[i];
flag = 1;
}
}
return;
}
AC code
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 11;
const int INF = 1e7;
int dep;int max_a;
int tmp[N],ans[N];
int a,b,c;
bool flag;
int GCD(const int x,const int y){
return y ? GCD(y, x % y) : x;
}
void dfs(const int a,const int b,const int x){
if(x > dep)
return;
if(a == 1){
tmp[x] = b;
if(!flag || tmp[dep] < ans[dep])
for(int i = 1;i <= dep;i++)
ans[i] = tmp[i];
flag = 1;
return;
}
if(x == dep - 1){
const int l = ((b << 2) / (a * a)) + 1,r = min(((max_a << 1)) / a,max_a * max_a / b);
for(int i = l;i <= r;i++){
int delta = a * a * i * i - ((b * i) << 2);
int Sqrt = sqrt(delta);
if(Sqrt * Sqrt != delta || ((a * i - Sqrt) & 1))
continue;
tmp[x] = (a * i - Sqrt) >> 1;tmp[x + 1] = (a * i + Sqrt) >> 1;
if(!flag || tmp[dep] < ans[dep]){
for(int i = 1;i <= dep;i++)
ans[i] = tmp[i];
flag = 1;
}
}
return;
}
int l = max((b + a - 1) / a,tmp[x - 1] + 1);
int r = min((((dep - x + 1) * b + a - 1) / a),max_a);
if(flag && r >= ans[dep])
r = ans[dep] - 1;
for(int i = l;i <= r;i++){
tmp[x] = i;
const int A = a * i - b,B = b * i;
const int gcd = GCD(A,B);
dfs(A / gcd,B / gcd,x + 1);
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> a >> b;
c = GCD(a,b);
a /= c;b /= c;
tmp[0] = 1;
for(dep = 1;dep <= N - 1;dep++){
ans[dep] = tmp[dep] = INF + 1;
for(max_a = 100000;max_a <= INF;max_a *= 10){
dfs(a,b,1);
if(flag){
for(int i = 1;i <= dep;i++)
cout << ans[i] << ' ';
return 0;
}
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)