[Solver] SPOJ FINFRAC

题目地址: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




你可能感兴趣的:(数论,解题报告)