如果直接用暴力求解估计逻辑混乱的理不清。经过分析其实用剩余定理来解答其实是最好的办法。先介绍剩余定理。
中国剩余定理分析
我们将“孙子问题”拆分成几个简单的小问题,从零开始,试图揣测古人是如何推导出这个解法的。
首先,我们假设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;
}