1. 程式人生 > >(模板)中國剩餘定理 重學筆記 POJ1006

(模板)中國剩餘定理 重學筆記 POJ1006

資料推薦

有很大幫助的部落格
——(本文多處引用其中金句,特此註明出處並鞠躬感謝博主。

主要定理

  • 定理一: 幾個數相加,如果任何一個數不能被div整除,那麼這幾個數的和一定不能被div整除。
  • 定理二: 兩數不能整除,若除數擴大(或縮小)了幾倍,而被除數不變,則其商和餘數也同時擴大(或縮小)相同的倍數(餘數必小於除數)。

主要步驟

  • 求出各個除數(div)的最小公倍數(lcm)。
  • 求出各個除數的基礎數(base),某個除數的基礎數應該滿足幾個性質:1. 能被除了這個除數以外的所有除數整除,2.除以這個除數的餘數符合題意。
  • 將所有基礎數求和後取模最小公倍數,結果即為答案。

相關證明

上面的那個部落格非常的經典,在此直接引用並深鞠躬感謝博主。

(1)最小公倍數就不用解釋了,跳過(記住,這裡討論的都是兩兩互質的情況)

(2)觀察求每個數對應的基礎數時候的步驟,比如第一個。105÷3=35。顯然這個35是除了當前這個數不能整除以外都能夠被其他數整除,就是其他數的最小公倍數。相當於找到了最小的開始值,用它去除以3發現正好餘2。那麼這個基礎數就是35。記住35的特徵,可以整除其他數但是不能被3整除,並且餘數是2。體現的還不夠明顯,再看下5對應的基礎數。21是其他數的最小公倍數,但是不能被5整除,用21除以5得到的餘數是1,而要求的數除以5應該是餘1的。所以餘數被擴大,就得到了相應的基礎數63。記住這個數的特徵,可以被其他數整除但是被5除應該餘三。同理,我們得到了第三個基礎數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的大小關係的。

我的程式碼

#include <cstdio>
#include <iostream>
using namespace std;

typedef long long ll;
const int maxn=int(20);
int n;
ll div[maxn],r[maxn];

long long exgcd(ll a,ll b,ll &x,ll &y) {
    if(b==0) {x=1,y=0; return a;}
    ll res=exgcd(b,a%b,x,y), tmp=y;
    y=x-y*(a/b), x=tmp;
    return res;
}

ll china(ll n,ll *div,ll *r) {
    ll lcm=1,d,y,x=0;
    for(int i=1;i<=n;i++)
        lcm*=div[i];
    for(int i=1;i<=n;i++) {
        ll base=lcm/div[i];
        exgcd(div[i],base,d,y);
        x=(x+y*base*r[i])%lcm;
    }
    return (x+lcm)%lcm;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);
#endif // ONLINE_JUDGE

    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld%lld",&div[i],&r[i]);

    printf("%lld\n",china(n,div,r));

    return 0;
}

例題 POJ1006

題目描述

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

題解

由題意可知,設滿足條件的那天是第res天,每個生理週期的週期長度分別為T1,T2,T3,每個峰值出現的日期分別為D1,D2,D3,則滿足以下等式:res=T1k+D1res=T2k+D2res=T3k+D3,其中k在各式中可以不相同且是任意整數;
由此可以列出同餘線性方程組,用中國剩餘定理求解即可。

程式碼

#include <cstdio>
#include <iostream>
using namespace std;

typedef long long ll;
long long exgcd(ll a,ll b,ll &x,ll &y) {
    if(b==0) {
        x=1, y=0;
        return a;
    } else {
        long long res=exgcd(b,a%b,x,y), tmp=y;
        y=x-y*(a/b), x=tmp;
        return res;
    }
}

long long China(int n,ll *div,ll *r) {
    long long lcm=1,x=0;
    for(int i=1;i<=n;i++)
        lcm*=div[i];
    for(int i=1;i<=n;i++) {
        long long base=lcm/div[i],tmp,cur;
        exgcd(div[i],base,tmp,cur);
        x=(x+cur*base*r[i])%lcm;
    }
    return (x+lcm)%lcm;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);
#endif // ONLINE_JUDGE

    long long div[5]={0,23,28,33},r[5]={};
    int cur,n=3,cas=0;

    while(scanf("%lld%lld%lld%d",&r[1],&r[2],&r[3],&cur)==4 && ~cur) {
        for(int i=1;i<=n;i++)
            r[i]%=div[i];
        long long res=China(n,div,r)-cur;
        if(res<=0) res+=21252;
        if(res>21252) res-=21252;
        printf("Case %d: the next triple peak occurs in %lld days.\n",++cas,res);
    }

    return 0;
}