埃及分数问题——迭代加深搜索

问题描述:在古埃及,人们使用单位分数的和(即1/a,a是自然数)表示一切有理数。例如,2/3=1/2+1/6,但不允许2/3=1/3+1/3,因为加数中不允许有相同的,则最小的分数越大越好。例如19/45=1/5+1/6+1/18是最优方案。

分析:题目看似不难,但是有陷阱,没有给出深度,一味回溯,肯定出不来,作者给出的思路就是用迭代法,加剪枝。每次枚举深度上限,每次都判断是否比之前的解更小,每次搜索时候判断当前的数x剩下的深度是不是小于剩下的部分,如果小于直接退出,因为下面的枚举分数只会越来越小。

看看里面用到的数学知识,实现求一个分数的分解的第一项,要使它最大(然后从从这个最小分母开始遍历,相当于起点)。例如495/499=1/2+1/5+1/6+1/8+1/3992+1/14970,要求起点,也就是1/2,同时正好满足最终解,这里面相当于简单除法,\frac{495}{499}\geqslant \frac{1}{c},要使c最大,也就是1/c最小,\frac{1}{\frac{499}{495}}\geqslant \frac{1}{c}c=\frac{499}{495}+1

欧几里得算法,也叫辗转相除法,是数论里面广为人知的算法,作用是求最大公约数。

定理:两个整数的最大公约数等于其中较小的那个数和两数相除余数的最大公约数。

举例说明:

18 和10的最大公约数

18=1*10+8

10=1*8+2

8=2*4+0

所以最大公约数为2

证明:

第一种:

设a=kb+r,r=a-kb

设a,b的最大公约数为n,r/n=a/n-kb/n

因为a/n和kb/n都能整除,所以r也可以被n整除,所以a,b和r的最大公约数是n。

第二种:

设a,b的最大公约数为x,a=mx,b=nx

r=mx-knx=(m-kn)x,所以r是可以被x整除。

所以r和a,b的最大公约数是x

程序实现:

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

主体实现:

#include<cstdio>
#include<cstring>
#include<cctype>
#include<queue>
#include<iostream>
using namespace std;
const int maxn = 100 + 5;
typedef long long LL;
int maxd;
LL v[maxn],ans[maxn];
int get_first(LL a,LL b) {
	return b / a + 1;//取分子为1的最大组成分数
}
LL gcd(LL a, LL b) {
	return b == 0 ? a : gcd(b, a%b);
}
bool better(int d) {
	for (int i = d; i >= 0; i--)if (v[i] != ans[i]) {
		return ans[i] == -1 || v[i]<ans[i];//分母越大分数越小,最小的分数越大越好
	}
	return false;
}
bool dfs(int d, int from, LL a, LL b) {
	if (d == maxd) {
		if (b%a)return false;//如果不能整除就返回
		v[d] = b / a;
		if (better(d))memcpy(ans, v, sizeof(LL)*(d + 1));
		return true;
	}
	from = max(from, get_first(a, b));
	bool ok = false;
	for (int i = from;; i++) {//从from开始分母依次增大
		if (b*(maxd - d + 1) <= i * a)break;//剪枝,这里也是分母太大,分数很小的时候,无穷循环结束的时候
		v[d] = i;
		//计算剩余部分
		LL bb = b * i;
		LL aa = a * i - b;
		LL g = gcd(aa, bb);//最大公约数
		if (dfs(d + 1, i + 1, aa / g, bb / g))ok = true;//回溯
	}
	return ok;
}
int main() {
	int a=0, b=0,kase=0;
	while (cin >> a >> b) {
		int ok = 0;
		for (maxd = 1; maxd <= 100; maxd++) {
			memset(ans, -1, sizeof(ans));
			if (dfs(0, get_first(a, b), a, b)) { ok = 1; break; }
		}
		cout << "Case " << ++kase << ": ";
		if (ok) {
			cout << a << "/" << b << "=";
			for (int i = 0; i < maxd; i++) cout << "1/" << ans[i] << "+";
			cout << "1/" << ans[maxd] << "\n";
		}
		else cout << "No solution.\n";
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值