ZOJ 1601:Integer Approximation
链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1601
题意:对于一个给定的浮点数A(0.1 <= A <= 10)和一个最大值 L(1 <= L <= 100,000),在 1 到 L 之间找出最佳的两个整数 N 和 D (1<= N, D <= L),使得 N / D 的结果和 A 最接近(即 | A - N / D | 最小)。例如对于圆周率 pi = 3.14159265358979,在 1 到 10000 之间 355 / 133 的结果最接近 pi 。
分析:从直观上来看,除了“穷举式”的依次寻找,似乎并没有什么特别的捷径可寻。而且题目中对于 L 的值较小,不会超过 10w,这也暗示我们解法就是在这个范围内做一个完全搜索。
显然如果除法的结果是相同的,则整数越小越好,因此我们应从小到大搜索,这样后续的较大的等效除法就不会覆盖前面的。注意起始搜索点要根据 A 和 1 之间的关系而定。例如如果 A < 1,这时候 N < D。因此应该先令 N = 1。反过来如果 A > 1,应该先令 D = 1。为了效率,我们应该以比较小的那个数字为基础进行逐个搜索,因为另一个数字计算后具有放大作用。所以用比较小的数字搜索的话,循环次数会小于基于比较大的数字进行搜索。但下面的代码,为了简单起见,一律基于 D 进行逐个搜索,并没有区别对待。
例如如果 A = 0.1,则我们从 N = 1, D = 10 开始搜索,基于 D 进行累加,N = D * 0.1 会变化的很慢,将降低效率。因此这时候应该基于 N 搜索即累加 N 。如果调整 D 的步进值为 max( 1, (int)(1/A + 0.5) ),则要从数学上分析如何取整的问题,也就是对 D 采用一个固定的步进值和基于 N 搜索未必是等价的(因为 D 作为整数,和 N/A 之间存在误差),比较麻烦。
下面的代码中,y / x 是我们要探查的当前整数除法,即 y 对应的是 N,x 对应的是 D。err 是 y / x 和浮点数 A 之间的相对误差。在选取第一对整数时,务必要注意 L 的限制。例如如果 A = 0.1,则不考虑 L 我们选取的第一对整数应该是 1/10。但是如果 L = 5,那么 1/10 是不可能选到的,第一对整数就应该是 1 / 5。
#include <stdio.h> #include <math.h> void FindResult(double A, int L, int* pN, int* pD) { double minerr = 100000, err; int x, y; if(A >= 1) { x = 1; y = (int)(x * A + 0.5); if(y > L) y = L; } else { y = 1; x = (int)(y / A + 0.5); if(x > L) x = L; } while(x <= L && y <= L) { err = fabs(x * A - y); if(err < minerr) { *pN = y; *pD = x; minerr = err; } ++x; y = (int)(x * A + 0.5); } } int main(int argc, char* argv[]) { double A; int L, N, D; while(scanf("%lf %ld", &A, &L) != EOF) { FindResult(A, L, &N, &D); printf("%ld %ld\n", N, D); } return 0; }