POJ-1006 Biorhythms (生物节律)
Time Limit: 1000MS | Memory Limit: 10000K | |
Total Submissions: 139159 | Accepted: 44664 |
Description
Since the three cycles have different periods, the peaks of the three cycles generally occur at different times. We would like to determine when a triple peak occurs (the peaks of all three cycles occur in the same day) for any person. For each cycle, you will be given the number of days from the beginning of the current year at which one of its peaks (not necessarily the first) occurs. You will also be given a date expressed as the number of days from the beginning of the current year. You task is to determine the number of days from the given date to the next triple peak. The given date is not counted. For example, if the given date is 10 and the next triple peak occurs on day 12, the answer is 2, not 3. If a triple peak occurs on the given date, you should give the number of days to the next occurrence of a triple peak.
Input
Output
Case 1: the next triple peak occurs in 1234 days.
Use the plural form ``days'' even if the answer is 1.
Sample Input
0 0 0 0 0 0 0 100 5 20 34 325 4 5 6 7 283 102 23 320 203 301 203 40 -1 -1 -1 -1
Sample Output
Case 1: the next triple peak occurs in 21252 days. Case 2: the next triple peak occurs in 21152 days. Case 3: the next triple peak occurs in 19575 days. Case 4: the next triple peak occurs in 16994 days. Case 5: the next triple peak occurs in 8910 days. Case 6: the next triple peak occurs in 10789 days.
Source
中国剩余定理(孙子定理)详解
问题:今有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?
说明白一点就是说,存在一个数x,除以3余2,除以5余三,除以7余二,然后求这个数。上面给出了解法。再明白这个解法的原理之前,需要先知道一下两个定理。
定理1:几个数相加,如果存在一个加数,不能被数a整除,那么它们的和,就不能被整数a整除。
定理2:两数不能整除,若被除数扩大(或缩小)了几倍,而除数不变,则其商和余数也同时扩大(或缩小)相同的倍数(余数必小于除数)。
这两个定理浅显易懂,自己找个例子就能理解,就不再赘述了。
现给出求解该问题的具体步骤:
1、求出最小公倍数
lcm=3*5*7=105
2、求各个数所对应的基础数
(1)105÷3=35
35÷3=11......2 //基础数35
(2)105÷5=21
21÷5=4......1
定理2把1扩大3倍得到3,那么被除数也扩大3倍,得到21*3=63//基础数63
3、105÷7=15
15÷7=2......1
定理2把1扩大2倍得到2,那么被除数也扩大2倍,得到15*2=30//基础数30
把得到的基础数加和(注意:基础数不一定就是正数)
35+63+30=128
4、减去最小公倍数lcm(在比最小公倍数大的情况下)
x=233%105=23
那么满足题意得最小的数就是23了。一共有四个步骤。下面详细解释每一步的原因。
(1)最小公倍数就不用解释了,跳过(记住,这里讨论的都是两两互质的情况)
(2)观察求每个数对应的基础数时候的步骤,比如第一个。105÷3=35。显然这个35是除了当前这个数不能整除以外都能够被其他数整除,就是其他数的最小公倍数。相当于找到了最小的开始值,用它去除以3发现正好余2。那么这个基础数就是35。记住35的特征,可以整除其他数但是不能被3整除,并且余数是2。体现的还不够明显,再看下5对应的基础数。21是其他数的最小公倍数,但是不能被5整除,用21除以5得到的余数是1,而要求的数除以5应该是余3的。所以余数被扩大,就得到了相应的基础数63。记住这个数的特征,可以被其他数整除但是被5除应该余3。同理,我们得到了第三个基础数23,那么他的特征就是:可以被其他数整除,但是不能被7整除,并且余数为2。
(3)第三步基础数加和,为什么要这样做呢?利用就是上面提到的定理1。
35+63+30=128。对于3来说,可以把63+30的和看作一个整体,应该他们都可以被3整除。看着上面写出的三个数的特征,运用定理1来说,就是在35的基础上加上一个可以被3整除的倍数,那么得到的结果依然还是满足原先的性质的,就是128除以同样还是余2的。同理,对于5还说,这个数被除之后会剩余3;对于7来说,被除之后剩余2。所以说,我们当前得到的这个数是满足题目要求的一个数。但是这个数是不是最小的,那就不一定了。
(4)应该不能确定是不是最小的数,这个时候就要用到他们的最小公倍数了。最小公倍数顾名思义,一定是一个同时被几个数整除的最小的一个数,所以减去它剩余下来的余数还是符合题意要求的。当然也同样可以运用定理1来解释,只不过是加法变成了减法,道理还是一样的。当然具体要不要剪还是要看和lcm的大小关系的。
稍微的总结一下:就是已知m1,m2,m3是两两互质的正整数,求最小的正整数x,使它被m1,m2,m3除所得的余数分别是c1,c2,c3。孙子定理的思想便是线分别求出被其中数mi整除余1而被两外两个数整除的数Mi(i=1,2,3),则所求数之一的便是c1M1+c2M2+c3M3。由此我们可以得到n个两两互质数的情况。证明上面已经一步一步给出。
那么,到此为止基本的中国剩余定理的内容我们以及了解了,包括解答方法。那么如何编码呢?按照上面这个思路去编码,其实并不难。一共分为四大步。但是,大多数人的困惑在于如何求取基础数。这里呢,提供两种方法:
(1)第一种就是一直递增,直到找到。例如:3的基础数,35是其他数的最小公倍数。那么就从35开始,一直自增,知道余数为2,便停止(利用while循环)。
(2)第二种方法呢就是辗转相除法上得来的。这里的例子体现的不够明显,应当看看去求取乘法逆元的过程,下面讲的内容和乘法逆元有很大的关系,所以还是看看的好。简单举个例子:
假设现在三个数分别是14,3,5,它们两两互质,且要求的数除以5余3。求5对应的基础数。有:
42÷5=8......2
5÷2=2......1
所以1=5-2*2=5-2*(42-8*5)=-2*42+17*5
那么-2*42=-84 17*5=85 -84+85=1
把1扩大3倍变成3,则有-84*3=-252也就是5对应的基础数。
第一点: 基础数可以是负数,这个之前点到过。//并且下面的解法就是有这样的。
第二点: 当得到余数为1的时候后面的算式相当于是一个回溯的过程,最后解到-2*42。 但是还只不过是余数是1的情况对应的数,再运用定理2我们就得到了-252这个基础数。实际上要是看过乘法逆元,这里实际就是乘法逆元的求解过程,而-2也就是42关于15取模的乘法逆元。
模板:(可以跳过这个代码)
/*long long gcd(LL a,LL b) { return b==0?a:gcd(b,a%b); }*/ #include<cstdio> #define ll long long //扩展欧几里得算法 void gcd(ll a,ll b,ll &d,ll &x,ll &y) { if(b==0){ d=a; x=1,y=0; } else{//else不能省略 gcd(b,a%b,d,y,x); y-=(a/b)*x; } } //中国剩余定理 ll China(int n,ll *m,ll *a) { ll M=1,d,y,x=0; for(int i=0;i<n;i++) M*=m[i]; for(int i=0;i<n;i++){ ll w=M/m[i]; gcd(m[i],w,d,d,y); x=(x+y*w*a[i])%M; } return (x+M)%M; } ll m[15],a[15]; int main() { int n; scanf("%d",&n); for(int i=0;i<n;i++) scanf("%lld%lld",&m[i],&a[i]); printf("%lld",China(n,m,a)); }
讲解二(思想一致)
POJ1006: 中国剩余定理的完美演绎
问题描述
人自出生起就有体力,情感和智力三个生理周期,分别为23,28和33天。一个周期内有一天为峰值,在这一天,人在对应的方面(体力,情感或智力)表现最好。通常这三个周期的峰值不会是同一天。现在给出三个日期,分别对应于体力,情感,智力出现峰值的日期。然后再给出一个起始日期,要求从这一天开始,算出最少再过多少天后三个峰值同时出现。
问题分析
首先我们要知道,任意两个峰值之间一定相距整数倍的周期。假设一年的第N天达到峰值,则下次达到峰值的时间为N+Tk(T是周期,k是任意正整数)。所以,三个峰值同时出现的那一天(S)应满足
S = N1 + T1*k1 = N2 + T2*k2 = N3 + T3*k3
N1,N2,N3分别为为体力,情感,智力出现峰值的日期, T1,T2,T3分别为体力,情感,智力周期。 我们需要求出k1,k2,k3三个非负整数使上面的等式成立。
想直接求出k1,k2,k3貌似很难,但是我们的目的是求出S, 可以考虑从结果逆推。根据上面的等式,S满足三个要求:除以T1余数为N1,除以T2余数为N2,除以T3余数为N3。这样我们就把问题转化为求一个最小数,该数除以T1余N1,除以T2余N2,除以T3余N3。这就是著名的中国剩余定理,我们的老祖宗在几千年前已经对这个问题想出了一个精妙的解法。依据此解法的算法,时间复杂度可达到O(1)。下面就介绍一下中国剩余定理。
中国剩余定理介绍
在《孙子算经》中有这样一个问题:“今有物不知其数,三三数之剩二(除以3余2),五五数之剩三(除以5余3),七七数之剩二(除以7余2),问物几何?”这个问题称为“孙子问题”,该问题的一般解法国际上称为“中国剩余定理”。具体解法分三步:
- 找出三个数:从3和5的公倍数中找出被7除余1的最小数15,从3和7的公倍数中找出被5除余1 的最小数21,最后从5和7的公倍数中找出除3余1的最小数70。
- 用15乘以2(2为最终结果除以7的余数),用21乘以3(3为最终结果除以5的余数),同理,用70乘以2(2为最终结果除以3的余数),然后把三个乘积相加(15*2+21*3+70*2)得到和233。(看懂之后,会发现与上面叙述求“基础数”的方法一样。)
- 用233除以3,5,7三个数的最小公倍数105,得到余数23,即233%105=23。这个余数23就是符合条件的最小数。
就这么简单。我们在感叹神奇的同时不禁想知道古人是如何想到这个方法的,有什么基本的数学依据吗?
中国剩余定理分析
我们将“孙子问题”拆分成几个简单的小问题,从零开始,试图揣测古人是如何推导出这个解法的。
首先,我们假设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为大于零的整数)。
完整代码:
#include <iostream>
using namespace std;
int Extended_Euclid(int a,int b,int &x,int &y) //扩展欧几里得算法
{
int d;
if(b==0)
{
x=1;y=0;
return a;
}
d=Extended_Euclid(b,a%b,y,x);
y-=a/b*x;
return d;
}
int Chinese_Remainder(int a[],int w[],int len) //中国剩余定理 a[]存放余数 w[]存放两两互质的数
{
int i,d,x,y,m,n,ret;
ret=0;
n=1;
for (i=0;i<len;i++)
n*=w[i];
for (i=0;i<len;i++)
{
m=n/w[i];
d=Extended_Euclid(w[i],m,x,y);
ret=(ret+y*m*a[i])%n;
}
return (n+ret%n)%n;
}
int main()
{
int n;
int w[3],a[]={23,28,33};
int k = 1;
while (~scanf("%d%d%d%d",&w[0],&w[1],&w[2],&n))
{
if ( w[0]==-1&&w[1]==-1&&w[2]==-1&&n==-1)
break;
int t = Chinese_Remainder(w,a,3) - n ; //引用中国剩余定理
t=t % ( 23 * 28 * 33 ) ;
if ( t <= 0 )
t += 23 * 28 * 33 ;
printf("Case %d: the next triple peak occurs in ",k) ;
printf("%d days.\n",t);
k++;
}
return 0;
}
看完了前辈的代码后这儿调用的中国剩余定理函数是做的一些改动
int Chinese_Remainder(int a[],int w[],int len) //中国剩余定理 a[]存放余数 w[]存放两两互质的数 { int i,d,x,y,m,n,ret; ret=0; n=1; for (i=0;i<len;i++) n*=w[i]; for (i=0;i<len;i++) { m=n/w[i]; d=Extended_Euclid(w[i],m,x,y); ret=(ret+y*m*a[i]);//%n;(也要保证在除以w[i]余a[i]的时候又是w[i+1],w[i+2]的最小公倍数,即m); } return ret%n;//(n+ret%n)%n; }