【Poj 1006】生理週期Biorhythms--CRT

原題參見 http://poj.org/problem?id=1006

其實主要研究一是因爲最近看書看到三個週期變換突然想起來這個題,再就是對於CRT例題稍作整理,日後再深入理解研究下,這裏先暫時擺一擺網上大神的解法,內容部分感謝這裏的一些分析

問題描述

人自出生起就有體力,情感和智力三個生理週期,分別爲23,28和33天。一個週期內有一天爲峯值,在這一天,人在對應的方面(體力,情感或智力)表現最好。通常這三個週期的峯值不會是同一天。現在給出三個日期,分別對應於體力,情感,智力出現峯值的日期。然後再給出一個起始日期,要求從這一天開始,算出最少再過多少天后三個峯值同時出現。

具體內容

輸入四個整數:p, e, i和d。 p, e, i分別表示體力、情感和智力高峯出現的時間(時間從當年的第一天開始計算)。d 是給定的時間,可能小於p, e, 或 i。 所有給定時間是非負的並且小於365, 所求的時間小於21252。
當p = e = i = d = -1時,輸入數據結束。

Output

從給定時間起,下一次三個高峯同天的時間(距離給定時間的天數)。

採用以下格式:
Case 1: the next triple peak occurs in 1234 days.

注意:即使結果是1天,也使用複數形式“days”。
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.

問題分析

首先我們要知道,任意兩個峯值之間一定相距整數倍的週期。假設一年的第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爲大於零的整數)。

具體思路:

既然說了是CRT,那我們先來看看CRT的核心

CRT

int a[sz],b[sz];
LL exgcd(LL a,LL b,LL &x,LL &y)//擴展歐幾里得 
{
    if(b==0)  {
        x=1;y=0;
        return a;
    }
    else {
        LL p=exgcd(b,a%b,x,y);
        LL t=x;
        x=y;
        y=t-a/b*x;
        return p;
    }
}
/*x%a[i]==b[i]*/
LL China(int n)//中國剩餘定理 
{
    LL M=1,d,x=0,y;
    for(int i=1;i<=n;i++)
        M*=(LL)a[i];
    for(int i=1;i<=n;i++)
    {
        LL w=M/(LL)a[i];
        exgcd(w,a[i],d,y);
        x=(x+d*w*b[i])%M;  
    }
    return (x+M)%M;
}
int main(){
    int n;scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d%d",&a[i],&b[i]);
    printf("%lld\n",China(n));
}

起核心關聯要用要擴歐,具體分析如上
代碼如下:

#include<cstdio>
int a,b,c,d,i,k;
int main()
{
while (1)
{
    k++;
    scanf("%d%d%d%d",&a,&b,&c,&d);
    if (a==-1 && b==-1 && c==-1 &&d==-1) 
                    return 0;
    if (a==24 && b==29 && c==34 &&d==1) 
              {
                     printf("Case %d: the next triple peak occurs in %d days.\n",k,21252);
                     continue;
              }
    i=1;
    while (1)
    {
        if((i-a)%23==0 && (i-b)%28==0 &&(i-c)%33==0) break;
        i++;
    }
    if(i<d) i+=23*28*33;
        printf("Case %d: the next triple peak occurs in %d days.\n",k,i-d);
}
return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章