题目地址:http://www.spoj.com/problems/FINFRAC/
题目大意:
给4个整数a,b,c,d,寻找两个整数p,q,使得a/b < p/q < c/d,需要q是最小的,如果存在多个解,那么找到p是最小的。
解法1(证明不严格):
有个序列叫做法雷序列,法雷序列的神奇之处在于如果a/b < c/d,则 a/b < (a+c)/(b+d) < c/d,那么还有 a/b < (2a+c)/(2b+d) < (a+c)/(b+d) < (a+2c)/(b+2d) < c/d
我们发现前面的分数“参与”的越多,则结果越接近前面的分数,反之亦然。
受此启发,设两个权重x>0和y>0(如果等于0,就不能严格大于小于了),有 a/b < (xa+yc)/(xb+yd) < c/d
可以证明(xa+yc)/(xb+yd)可以覆盖a/b和c/d之间的所有分数,所以我们要求的p/q也必然是这种形式。
题意要求p和q都是最小,那么就是(xa+yc)和(xb+yd)的gcd尽可能大,然后把gcd约分下去,分子分母才能比较小。
问题转化为求gcd(xa+yc,xb+yd),其中abcd已知,x,y为所求,换种写法gcd(ax+cy,bx+dy),这里可以利用欧几里得辗转相除,举个例子。
a = 7, b = 5, c = 8, d = 5,则gcd(7x+8y,5x+5y) = gcd(2x+3y,5x+5y) = gcd(2x+3y,3x+2y),这时没法继续了,因为一边x多,一边y多,那就假设它们相等,
即2x + 3y = 3x + 2y,解得x = y,代入原式得 (7x+8y)/(5x+5y) = 15x / 10x = 3/2。
代码如下:
#include <stdio.h> #define LL long long #define ABS(x) ( (x) < 0 ? -(x) : (x) ) LL gcd( LL a, LL b ) { while( b > 0 ) { LL t = a % b; a = b; b = t; } return a; } void solve( int a, int b, int c, int d, int& x, int& y ) { x = y = 1; int t1, t2; while(1) { t1 = t2 = 0x7fffffff; if( a >= b && c >= d ) { if( b > 0 ) t1 = a / b; if( d > 0 ) t2 = c / d; if( t1 > t2 ) t1 = t2; a -= t1 * b; c -= t1 * d; } else if( a <= b && c <= d ) { t1 = a; a = b; b = t1; t2 = c; c = d; d = t2; } else break; } x = ABS( c - d ); y = ABS( a - b ); if( x == 0 ) x = 1; if( y == 0 ) y = 1; } int main() { int a, b, c, d, x, y, tmp; LL p, q, tmp2; while( scanf( "%d%d%d%d", &a, &b, &c, &d ) == 4 ) { tmp = gcd( a, b ); a /= tmp; b /= tmp; tmp = gcd( c, d ); c /= tmp; d /= tmp; solve( a, b, c, d, x, y ); p = a * (LL)x + c * (LL)y; q = b * (LL)x + d * (LL)y; tmp2 = gcd( p, q ); printf( "%lld/%lld\n", p / tmp2, q / tmp2 ); } return 0; }
解法2(看了网上的答案):
设[a/b]表示a/b向下取整
2.1 如果a/b >= 1,设k = [a/b],可以知道 ( a/b ) - k < ( p/q ) - k < ( c/d ) - k,即 (a-bk)/b < (p - qk)/q < ( c- dk) / d,设a' = a - bk,p' = p - qk,c' = c - dk,则求出a'/b < p'/q < c'/d的解以后,p = p' + qk,可以得到真实的p和q。如果知道了a,b,q,那么p还有另一种求法(也就是说不需要从递归中获取p'也可以),就是p = q * a / b + 1(如果看成整数),这是因为p > q * a / b(如果看成浮点数),而整数运算除法会舍弃余数,所以正好加1。
2.2 如果a/b<1
2.2.1 如果c/d>1,那么p = q = 1
2.2.2 如果c/d<=1,那么问题可以转化为d/c < q/p < b/a
代码如下:
#include <stdio.h> #define LL long long LL findq( LL a, LL b, LL c, LL d ) { if( a < b ) { if( c > d ) return 1; else return findq( d, c, b, a ) * d / c + 1; } else { LL k = a / b; return findq( a - k * b, b, c - k * d, d ); } } int main() { LL a, b, c, d, p, q; while( scanf( "%lld%lld%lld%lld", &a, &b, &c, &d ) == 4 ) { q = findq( a, b, c, d ); p = q * a / b + 1; printf( "%lld/%lld\n", p, q ); } return 0; }
参考资料:
http://apps.topcoder.com/forums/;jsessionid=627E3C18A18ED2AA6A5B4BDB4F1A0C3D?module=RevisionHistory&messageID=1204829
wxcc的代码:http://acm.hust.edu.cn/vjudge/contest/viewSource.action?id=192084