洛谷题单指南-进阶搜索-P1763 埃及分数

原题链接:https://www.luogu.com.cn/problem/P1763

题意解读:将分数a/b拆分成若干个1/x的和,要求x递增,且拆分的个数越少越好,个数相同时最后一个x越小越好。

解题思路:

首先,比较明显的是可以用搜索来解决问题。

但是,如果用常规的DFS,搜索的深度有可能很深,因为每一个分母的最大值可以取到1e7,可能会爆栈;再考虑BFS,每一层状态数量也是巨大的,又可能会使得队列爆内存。

既然要求产分的个数越少越好,就可以想到对于搜索深度进行限制,逐渐递增,即迭代加深搜索。

通过每次枚举一个拆分项的分母,从a/b减掉该项后不断递归。

有几种重要的剪枝:

1、如果搜索深度超过当前最大深度,则返回

2、每次对拆分项分母枚举时,应该至少比上一次的大,并且a/b >= 1/x,所以x的枚举起点是max(上一次的分母+1,b/a+1)

3、每次枚举的拆分项,显然是后面结果中最大,如果接下来所有拆分项都是这个值,加起来都<=a/b,那么显然无解,也就是不满足(maxdepth-depth+1)*1/x<=a/b时可以提前结束枚举,加上此剪枝后,算法就有迭代加深变成了启发性搜索IDA*

仅有以上优化,还无法得到100分,此题还有一个关键的优化:对分母最大值的限制:1e7

如果每次都判断分母不超过1e7,也是比较耗时,这里就引入一种双重迭代加深方法:

第一层,针对搜索深度进行迭代加深,每次加1

第二层,针对分母最大限制进行迭代加深,初始1000,每次乘10

这样在dfs过程中,如果当前分数的分母b>最大分母限制,就可以提前结束了,这是因为a/b如果b超过maxb,那么a/b拆解出最后一个分数1/x的x必然大于maxb。

小知识:为什么迭代加深搜索不会超时?

迭代加深从限制深度1开始,每次加1,不会超时,因为假设在深度k的时候找到答案,且每一层都是满状态,以满二叉树为例,最后一层状态数为2^(k-1),而前k-1层状态数加起来也不过2^k-1 - 2^(k-1),还是在2^(k-1)级别,与最后一层复杂度同级别,所以最后一层不超时的情况,整体并不会导致超时。

100分代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

ll a, b;
vector<int> tmp, ans;

ll gcd(ll a, ll b)
{
    return b == 0 ? a : gcd(b, a % b);
}

//a/b表示当前的分数,depth表示当前深度,start表示下一个加数的分母的起始值,maxb表示分母的最大值,maxdepth表示最大深度
void dfs(ll a, ll b, int depth, ll start, int maxb, int maxdepth)
{
    if(depth > maxdepth) return; //剪枝,如果当前深度大于最大深度,那么就不用继续搜索了
    ll d = gcd(a, b);
    a /= d, b /= d; //将a和b约分
    if(b > maxb) return; //***剪枝,如果b大于最大分母,那么就不用继续搜索了
    if(a == 1 && b > tmp.back()) //最后一个加数的分母必须是最大的
    {
        tmp.push_back(b);
        if(ans.size() == 0 || (tmp.size() == ans.size() && tmp.back() < ans.back())) ans = tmp;
        tmp.pop_back(); 
        return;
    }
    
    start = max(start, b / a + 1); //剪枝,从b/a+1开始搜索,这样可以避免重复搜索
    for(int i = start; ; i++) //枚举下一个加数的分母
    {
        if(b * (maxdepth - depth + 1) < a * i) break; //剪枝,如果当前深度到最大深度的剩余搜索空间都不够,那么就不用继续搜索了
        if(i > maxb) break; //剪枝,如果当前分母大于最大分母,那么就不用继续搜索了
        tmp.push_back(i);
        dfs(a * i - b, b * i, depth + 1, i + 1, maxb, maxdepth);
        tmp.pop_back();
    }
} 

int main()
{
    cin >> a >> b;
    for(int maxdepth = 1; maxdepth <= 10; maxdepth++) //迭代加深最大深度
    {
        for(int maxb = 1000; maxb <= 10000000; maxb *= 10) //迭代加深分母的最大值
        {
            dfs(a, b, 1, 1, maxb, maxdepth);
            if(ans.size()) break; //如果找到解,那么就不用继续增加最大分母
        }
        if(ans.size()) break; //如果找到解,那么就不用继续增加最大深度
    }
    for(auto v : ans) cout << v << " ";
    return 0;
}

 

posted @   五月江城  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示