埃及分数(迭代加深搜索 + A*)

埃及分数

在古埃及,人们使用单位分数的和(形如1/a 的,a是自然数)表示一切有理数。例如2/3=1/2+1/6,但不允许2/3=1/3+1/3,因为加数中有相同的。对于一个分数a/b,表示方法有很多种,但是哪种最好呢?首先,加数少的比加数多的好,其次,加数个数相同的,最小的分数越大越好。例如:
19/45=1/3+1/12+1/180
19/45=1/3+1/15+1/45
19/45=1/3+1/18+ 1/30
19/45-1/4+1/6+1/180
19/45= 1/5+1/6+1/18
最好的是最后一种,因为1/18比1/180.1/45.1/30都大。给出a.b(0<a<b<1000),编程计算最好的表达方式。
        这道题目理论上可以用回溯法来解,但是解答树非常“恐怖”,不仅深度没有明显的上界, 而且加数的选择在理论上也是无限的。换句话说,如果用宽度优先遍历,连一层都扩展不完(因为每一层都是无限大的) 。
        解决方案是采用迭代加深搜索(iterative deepening):从小到大枚举深度上限maxd,每次执行只考虑深度不超过maxd的结点。这样,只要解的深度有限,则一定可以在有限时间内枚举到。
                                                ———摘自lrj的紫书

下面的代码从lrj的代码改编而来:

// 埃及分数问题
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

int maxd;

typedef long long LL;

LL gcd(LL a, LL b) {		//找到a、b的最小公倍数
	return b == 0 ? a : gcd(b, a%b);
}

// 返回满足1/c <= a/b的最小c
inline int get_first(LL a, LL b) {	//找到最大的1/c,使得1/c<a/b
	if (b%a == 0)
		return b / a;
	else
		return b / a + 1;
}

const int maxn = 100 + 5;
LL v[maxn], ans[maxn];

// 如果当前解v比目前最优解ans更优,更新ans
bool better(int d) {
	for (int i = d; i >= 0; i--) if (v[i] != ans[i]) {
		//ans[i] == -1,说明前面深度较小的过程都没有得到正确解
		//v[i]<ans[i]  在那些个数相同的解中,找到最小分数最大的解
		return ans[i] == -1 || v[i] < ans[i];
	}
	return false;
}

// 当前深度为d,分母不能小于from,分数之和恰好为aa/bb
bool dfs(int d, int from, LL aa, LL bb) {
	if (d == maxd-1) {
		if (bb % aa) return false; // aa/bb必须是埃及分数,aa为1
		if (bb == v[d-1]) return false;	//之前哪个数使用过,即最后两个数相同
		v[d] = bb / aa;
		if (better(d)) memcpy(ans, v, sizeof(LL) * (d + 1));
		return true;
	}
	bool ok = false;
	from = max(from, get_first(aa, bb)); // 枚举的起点
	for (int i = from; ; i++) {
		// 剪枝:如果剩下的maxd+1-d个分数全部都是1/i,加起来仍然不超过aa/bb,则无解
		if (bb * (maxd - d) < i * aa) break;
		v[d] = i;
		// 计算aa/bb - 1/i,设结果为a2/b2
		LL b2 = bb * i;
		LL a2 = aa * i - bb;
		LL g = gcd(a2, b2); // 以便约分
		if (dfs(d + 1, i + 1, a2 / g, b2 / g)) ok = true;
	}
	return ok;
}

int main() {
	int a, b;
	cin >> a >> b;
	int ok = 0;
	for (maxd = 1; maxd <= 100; maxd++) {		//从1层逐渐迭代到100层,知道找到解
		memset(ans, -1, sizeof(ans));
		if (dfs(0, get_first(a, b), a, b)) {
			cout << a << "/" << b << "=";
			for (int i = 0; i < maxd - 1; i++) cout << "1/" << ans[i] << "+";
			cout << "1/" << ans[maxd - 1] << endl;
			return 0;
		}
	}
	cout << "No solution" << endl;
	return 0;
}

这里有两个链接,关于埃及分数,讲得很不错。
简书 埃及分数 (贪心算法简单示例)
埃及分数 题解

发布了17 篇原创文章 · 获赞 2 · 访问量 429

猜你喜欢

转载自blog.csdn.net/Raymond_YP/article/details/104434923