用中国剩余定理解 POJ1006

POJ 1006有这样一道题目:

http://poj.org/problem?id=1006,大意是每个人在一生中都有三个生理循环:身体,情绪和智力。这三个生理循环的周期分别是23,28和33天。在这23,28和33天内,这三个生理循环都会有一个顶点,在顶点,人们在身体,情绪和智力方面都会有更好的表现。

 因为这三个生理循环有不同的周期,所以他们到达顶点的时间往往也是不同的。现在要求找出这三个顶点同时出现的时间点。

 输入:

一共有很多行,每行包含四个数字:p,e,i,d.  p,e i分别表示今年出现的身体,情绪和智力顶点的日子(可能不是第一个),d表示给定的日期,可能比p,e,i要小。输入以p = e =i =d =-1结束。

输出:

输出下一个顶点日距离给定日子d的距离。以这样的形式:

Case 1:the next triple peak occurs in 1234 days.


这实际上是中国剩余定理的一个应用。中国剩余定理,又称孙子定理,取自<<孙子算经>>中的“物不知数”问题:有物不知其数,三个一数余二,五个一数余三,七个一数又余二,问该物总数几何?

就是说一个数用三除余二,用五除余三,用七除用余二,问这个数是几。

解法如下:

      1.先找到35(5 * 7)的倍数,要求除三余1, 这里可以看出最小是70

      2. 找到 21(3 * 7)的倍数,要求除五余1, 这里最小是21,

      3. 找到15(3 * 5)的倍数,要求除7余1, 这里最小是15

用70 * 2 + 21* 3 + 15 * 2 = 233,233 %(3 * 5 * 7) = 23,23是最小的解,实际上23 + (3*5*7)*k ,k >= 0都是题目的解。


为什么这个算法是可行的呢? 对于三个两两互质的数字, i, j , k,我们有gcd(i,j) = gcd(i,k) = gcd(j,k) = 1. 我们令M1 = j * k, ,M2 = i * k, M3 = i * j,必然存在t1,t2,t3

  • t1 * M1 = 1 (mod i)
  • t2 * M2 = 1 (mod j)
  • t3 * M3 = 1(mod k)

t1,t2,t3分别称作M1,M2,M3的逆元。

假如有一个数余i等于a1, 余j等于 a2, 余k等于 a3,那么有

  • a1 * t1 * M1 = a1(mod i)
  • a2 * t2 * M2 = a2(mod j)
  • a3 * t3 * M3 = a3(mod k)

那么(a1 * t1 * M1 + a2 * t2 * M2 + a3 * t3 * M3) % i = (a1 * t1 * M1) % i + 0 + 0 = a1(因为M2和M3都是i的倍数,所以余i得0)

         (a1 * t1 * M1 + a2 * t2 * M2 + a3 * t3 * M3) % j = 0 + (a2 * t2 * M2) % j  + 0 = a1(因为M1和M3都是j的倍数,所以余j得0)

          (a1 * t1 * M1 + a2 * t2 * M2 + a3 * t3 * M3) % k = 0 + 0 + (a3 * t3 * M3) % k = a1(因为M1和M2都是k的倍数,所以余k得0)

可以看出(a1 * t1 * M1 + a2 * t2 * M2 + a3 * t3 * M3) 是一个解。又因为 i * j * k是可以被i,j,k三者整除,所以

(a1 * t1 * M1 + a2 * t2 * M2 + a3 * t3 * M3)  + (i * j * k)* n,n >= 0都是问题的解。


poj 1006考察的就是对这个定理的了解程度。所以剩下的问题就是如何求解t1, t2和t3使得

  • t1 * M1 = 1 (mod i)
  • t2 * M2 = 1 (mod j)
  • t3 * M3 = 1(mod k)

    这一般可由扩展欧几里得算法解除,对于本题,可以事先用暴力搜索找出这些值,t1 = 5544, t2 = 14421, t3 = 1428,剩下的问题就是套用剩余定理的公式了。


    最后贴上accept的代码,仅供抛砖引玉之用:

    #include <stdio.h>
    #define ALL_PRODUCTION (23 * 28 * 33)
    int main()
    {
    	int p,e,i,d,k;
    	for(int j = 0;;j++)
    	{
    		scanf("%d %d %d %d",&p,&e,&i,&d);
    		if(p == -1)
    			break;
    		else
    		{
    			k = 5544 * p + 14421 * e + 1288 * i;
    			k = k % ALL_PRODUCTION;
    			k = k - d;
    			if(k <= 0)
    			{
    				k += ALL_PRODUCTION;
    			}
    			printf("Case %d: the next triple peak occurs in %d days.\n",j + 1, k);
    		}
    	}
    	return 0;
    }



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值