POJ 1006 && 剩余定理 && 最小公倍数最大公约数

如果直接用暴力求解估计逻辑混乱的理不清。经过分析其实用剩余定理来解答其实是最好的办法。先介绍剩余定理。

中国剩余定理分析

  我们将“孙子问题”拆分成几个简单的小问题,从零开始,试图揣测古人是如何推导出这个解法的。

    首先,我们假设n1是满足除以3余2的一个数,比如2,5,8等等,也就是满足3*k+2(k>=0)的一个任意数。同样,我们假设n2是满足除以5余3的一个数,n3是满足除以7余2的一个数。

    有了前面的假设,我们先从n1这个角度出发,已知n1满足除以3余2,能不能使得 n1+n2 的和仍然满足除以3余2?进而使得n1+n2+n3的和仍然满足除以3余2?

    这就牵涉到一个最基本数学定理,如果有a%b=c,则有(a+kb)%b=c(k为非零整数),换句话说,如果一个除法运算的余数为c,那么被除数与k倍的除数相加(或相减)的和(差)再与除数相除,余数不变。这个是很好证明的。

    以此定理为依据,如果n2是3的倍数,n1+n2就依然满足除以3余2。同理,如果n3也是3的倍数,那么n1+n2+n3的和就满足除以3余2。这是从n1的角度考虑的,再从n2,n3的角度出发,我们可推导出以下三点:

为使n1+n2+n3的和满足除以3余2,n2和n3必须是3的倍数。

为使n1+n2+n3的和满足除以5余3,n1和n3必须是5的倍数。

为使n1+n2+n3的和满足除以7余2,n1和n2必须是7的倍数。

   因此,为使n1+n2+n3的和作为“孙子问题”的一个最终解,需满足:

n1除以3余2,且是5和7的公倍数。

n2除以5余3,且是3和7的公倍数。

n3除以7余2,且是3和5的公倍数。

   所以,孙子问题解法的本质是从5和7的公倍数中找一个除以3余2的数n1,从3和7的公倍数中找一个除以5余3的数n2,从3和5的公倍数中找一个除以7余2的数n3,再将三个数相加得到解。在求n1,n2,n3时又用了一个小技巧,以n1为例,并非从5和7的公倍数中直接找一个除以3余2的数,而是先找一个除以3余1的数,再乘以2。

   这里又有一个数学公式,如果a%b=c,那么(a*k)%b=a%b+a%b+…+a%b=c+c+…+c=kc(k>0),也就是说,如果一个除法的余数为c,那么被除数的k倍与除数相除的余数为kc。展开式中已证明。

   最后,我们还要清楚一点,n1+n2+n3只是问题的一个解,并不是最小的解。如何得到最小解?我们只需要从中最大限度的减掉掉3,5,7的公倍数105即可。道理就是前面讲过的定理“如果a%b=c,则有(a-kb)%b=c”。所以(n1+n2+n3)%105就是最终的最小解。

总结

   经过分析发现,中国剩余定理的孙子解法并没有什么高深的技巧,就是以下两个基本数学定理的灵活运用:

如果 a%b=c , 则有(a+kb)%b=c (k为非零整数)。

如果 a%b=c,那么(a*k)%b=kc (k为大于零的整数)。

 

—————————————————————————————————————————————————————————-

 

那么对于本道题。很显然的就有如下关系:

n % 23 ==p; n % 28 == e;n % 33 == i

按照剩余定理,有:

使33 * 28 * a % 23 = 1, 

使23* 33 * b % 28 = 1,
使23 * 28 * c % 33 = 1

 

所以需要求ABC三个数中AB的公倍数中除以C余一最小的那个。

所以这里再介绍一下求解最小公倍数和最大公约数的方法:

 

求最小公倍数的算法:

最小公倍数 = 两数成绩 / 最大公约数

求最大公约数的算法:

1,辗转相除法

  有两个整数a和b

1)a%b得余数c

2)若c = 0; 则b即为两数的最大公约数

3)若c != 0; 则a = b, b = c,再回去执行1);

例如求27和15的最大公约数过程为:

27÷15 余1215÷12余312÷3余0因此,3即为最大公约数

 

2,相减法

  有两个整数a和b。

       若a > b  则a = a – b

       若a < b  则b = b – a

       若a = b  则a或者b即为两数的最大公约数

       若a != b   再执行一次。

例如求27和15的最大公约数过程为:

27-15=12(15>12 ) 15-12=3( 12>3 )

12-3=9(9>3 ) 9-3=6( 6>3 )

6-3=3(3==3 )

因此,3即为最大公约数

 

3,穷举法

有两整数a和b:

i=1

若a,b能同时被i整除,则t=i

i++

若 i<= a(或b),则再回去执行

若 i> a(或b),则t即为最大公约数,结束

改进:

i= a(或b)

若a,b能同时被i整除,则i即为最大公约数,

结束

i–,再回去执行

 

然后用辗转相除写一个程序来进行计算上述的三个abc

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main()
{
    int a, b, e, c, d;
    int x, y;

    //输入的时候保证从小到大输入
    scanf("%d%d%d", &a, &b, &e);
    x = a;
    y = b;

    while (b != 0)
    {
        c = a % b;
        a = b;
        b = c;
    }
    //输出最大公约数和最小公倍数
    printf("%d\n", a);
    printf("%d\n", x * y / a);

    d = x * y / a;
    for(;;)
    {
        if((d % e) == 1)
        {
            printf("%d\n", d);
            break;
        }
        else
        {
            d += x * y / a;
            continue;
        }
    }
    scanf("%d%d%d", &a, &b, &e);

    return 0;
}

代码运行后,

得a= 6; 33 * 28 * 6 = 5544; 

b = 19;23 * 33 * 19 = 14421; 

c = 2;  23 * 28 * 2 = 1288。 

那么n =  5544 * p + 14421 * e + 1288 * i

那么n-d即相差的时间天数!

因为有范围限制,那么(n-d) %= 21252;且如果此时<=0,那么(n-d)  += 21252

原题代码如下: 

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define PAI 3.1415926

int main()
{
    int p, e, i, d;
    int n, N = 0;

    while(scanf("%d%d%d%d", &p, &e, &i, &d))
    {
        if(p == -1 && e == -1 && i == -1 && d == -1)
            break;
    else
        n = (5544 * p + 14421 * e + 1288 * i - d + 21252) % 21252;
        if(n == 0)
            printf("Case %d: the next triple peak occurs in %d days.\n", ++N, 21252);
        else
            printf("Case %d: the next triple peak occurs in %d days.\n", ++N, n);
    }

    return 0;
}
点赞