题目地址: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;
}
参考资料:
wxcc的代码:http://acm.hust.edu.cn/vjudge/contest/viewSource.action?id=192084