UVa 12558 Egyptian Fractions (HARD version) 埃及分数(困难版) IDA 迭代加深搜

题目链接:Egyptian Fractions (HARD version)
题目描述:

给定一个分数,你需要将其拆成多个分子为 1 1 1的分数相加,要求出现的分数不能重复,同时会给定 k k k个数字,这些数字不能出现在分母中,你需要按照分母从小到大进行输出,如果有多个解满足条件你需要输出拆解的分数个数尽可能少的解答,若还有多个解,你需要输出最小的分数尽可能大,如果依然有多个解,你需要让第二小的分数尽可能大,以此类推。题目保证输入数据不会特别难分解。
例如:
在这里插入图片描述
其中 C a s e 5 Case 5 Case5是不能使用数字 33 33 33时的解。

题解:

首先本题不能使用浮点数来进行分数的计算,这样的话精度不够(原题的题面中已经明确说明)。然后本题我们可以使用 I D A IDA IDA进行解答,也就是迭代加深搜。然后就是枚举当前分母可能的值,然后进行搜索,我们搜索的时候可以带上状态,表示当前需要组成的分数,搜索下一层的时候用当前的分数减去当前枚举的分数作为下一层需要搜索的分数。这个过程需要使用到 G C D GCD GCD算法(辗转相除法)进行约分。
现在唯一需要解决的问题就是枚举分母的上下限。
我们先来考虑分母的下界,首先为了防止重复枚举,我们需要让下一层的分母比上一层的分母大,这样的话,分母的一个下限就是上一层的枚举值加一,同时我们还需要保证当前枚举的分数比需要组成的分数小,因为题目中要求了不能出现负数,如果当前需要枚举的分数是 a b \frac a b ba我们设当前的分母最小值为 c c c那么有, a b ≥ 1 c \frac a b \ge \frac 1 c bac1整理可以得到 c ≥ b a c\ge \frac b a cab由于必须是整数我们需要将不等式右边进行向上取整,这样下界就可以确定为上述两个值的最大值。
接下来就是分母的上界,首先我们很容易想到,当前的上界的不能超过当前记录的答案分母的最大值,因为如果超过了这个值,那么当前记录的答案更优的,所以没有继续进行搜索的必要,但是上面的上界确定只有在搜索出一个答案的时候才能进行使用,那么如果没有搜索出答案的时候上界应该如何确定了?我们记还有 n n n个分数需要搜索,那么对于 a b \frac a b ba我们让当前的位置的分数尽可能的小,同时由于后续的分数必须必当前更小,我们可以假设后续的分数都相等来确定当前位置分母搜索的上界(如果比这个还大,那么后续的分母只有比当前的分母小才能组成当前要求的分数,这就发生了重复搜索),也就是 n b a \frac {nb} a anb这里我们需要向下取整。

代码:

#include 

const int MAX_C = 1000;

using namespace std;

vector<long long> denominator;
vector<long long> ans;
int a, b, k, caseID, T, c, maxDepth;
bool ok[MAX_C + 1];
bool done;

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

void dfs(int nowDepth, long long a, long long b)
{
    if (nowDepth == maxDepth) {
        if (a == 0) {
            if (ans.size() == 0) {
                ans = denominator;
            } else {
                bool needUpdate = false;
                for (int i = nowDepth - 1; i >= 0; i--) {
                    if (denominator[i] > ans[i]) {
                        break;
                    } else if (denominator[i] < ans[i]) { // 当前答案的最小的分数更加大(分母小的分数更大)或者第二小的更大...
                        needUpdate = true;
                        break;
                    }
                }
                if (needUpdate) { ans = denominator; }
            }
            done = true;
        }
        return;
    }
    long long nowDenomitor = b / a; // 当前的分数要比a/b小,那么当前的分母最小为b/a
    long long maxNowDenominator = (maxDepth - nowDepth) * b / a; // 由于后续的分数的分母要比当前选择的分母大,那么当前的分母最大值不能超过后续分母全部相等的情况
    if (b % a != 0) { nowDenomitor++; }
    if (nowDepth != 0) { nowDenomitor = max(nowDenomitor, denominator[nowDepth - 1] + 1); }
    if (ans.size() != 0) { maxNowDenominator = min(maxNowDenominator, ans[ans.size() - 1]); } // 上限不应该比当前记录的最小的分数还要大,刚好是最后一个的时候,可能会相等
    for (; nowDenomitor <= maxNowDenominator; nowDenomitor++) {
        if (nowDenomitor <= 1000 && !ok[nowDenomitor]) { continue; }
        denominator.push_back(nowDenomitor);
        long long na = a * nowDenomitor - b;
        long long nb = b * nowDenomitor;
        long long g = gcd(na, nb);
        dfs(nowDepth + 1, na / g, nb / g);
        denominator.pop_back();
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin >> T;
    while (T--) {
        done = false;
        denominator.resize(0);
        ans.resize(0);
        memset(ok, 1, sizeof(ok));
        caseID++;
        cin >> a >> b >> k;
        for (int i = 0; i < k; i++) {
            cin >> c;
            ok[c] = false;
        }
        for (maxDepth = 1; ; maxDepth++) {
            dfs(0, a, b);
            if (done) { break; }
        }
        cout << "Case " << caseID << ": ";
        cout << a << "/" << b << "=";
        for (size_t i = 0; i < ans.size(); i++) {
            if (i != 0) { cout << "+"; }
            cout << "1" << "/" << ans[i];
        }
        cout << endl;
    }
    return 0;
}

你可能感兴趣的:(算法题目,迭代加深,算法,c++)