用中國剩餘定理解 POJ1006

Vally1989發表於2016-04-02
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;
}



相關文章