1. 程式人生 > >ACM 數論知識 合集

ACM 數論知識 合集

寫在最前面

要是看到數論的題,就一定別當這是到程式設計題,其實,只是數學題。把能用上的數學方法都可以用上,相信自己的直覺,還是很關鍵的。

關於歐幾里得的那些事

真是醉了啊,剛才寫了兩個小時的博文,想儲存到草稿箱裡,結果顯示伺服器異常,結果返回一看,臥槽,寫的都沒了,心中是萬千草泥馬呼嘯而過呀。。。還得從新寫呀。

歐幾里得演算法

最大公約數問題是最早被研究的演算法問題之一了,並且是ACM競賽中能涉及到的很 多數論內容,比如模線性方程,模線性方程組的基礎。歐幾里得演算法 (Euclidean algorithm) ,即大部分選手所知的“輾轉相除法”,其核心在於不斷將兩數規模變小,最後實 現對數時間內把問題變換到能直接判定解的規模。
上個程式碼:

int gcd(int a, int b)
{
    if(b == 0) return a;
    return gcd(b, a%b);
}

拓展歐幾里得演算法

拓展歐幾里得演算法(Extended Euclidean Algorithm)是基於歐幾里得演算法而來解 一類特殊的線性丟番圖方程(Diophantine equation):

ax + by = gcd(a,b)

而當gcd(a,b) = 1,即a,b互質的時候,這個方程的解實際上就對應了a關於模b的逆元。
下面是從歐幾里得演算法拓展的過程:
首先呢,可以看出:a = gcd(a,b), b = 0 是該式的一個特解;
然後,根據歐幾里得演算法的原理可以得出:bx + (a%b)y = gcd(a,b);
又因為, a%b = a - (a/b)*b (a/b 為整除關係)
所以原式化為: bx + (a - (a/b)*b)y = gcd(a,b);
整理得: ay + b * (x - (a/b) * y) = gcd(a,b);
所以解之間的遞迴關係為:
- xx = y;
- yy = x - (a/b)y;

所以,將上面的過程整理一下,用程式碼實現如下:

int extend_Euclid(int a, int b, int &x, int &y)
{
    if(b==0)
    {
        x = 1;
        y = 0;
        return a;
    }
    int r = extend_Euclid(b, a%b, y, x);
    y -= a/b*x; //這裡已經是遞迴,回溯的過程了,x,y已經顛倒了
    return r;
}

同時,可以給出通解的方程:

  • x = x0 + b/gcd(a,b)*t;
  • y = y0 - a/gcd(a,b)*t;
    (x0, y0 為方程的一組特解, t為整數)

證明過程呢,看過好幾個博文,不是證法古怪,就是YY的。
這裡給出我自己的證法思路。
可以將線性的丟番圖公式看做一個直線的方程,而通解就是直線方程的一個引數方程。

用法

求解不定方程

 一般的,只要是能夠化簡成形如丟番圖公式的,往往可以用拓展歐幾里得演算法來解決的

先上道例題吧,

pku 1061青蛙的約會

先說一下大概題意:有兩隻青蛙,一隻在座標x,另一直在座標y,青蛙x一次跳躍可以前進m單位距離,青蛙y一次跳躍可以前進n單位的距離,兩青蛙都在同一緯度,該緯度長度為L。兩隻青蛙同方向同時跳啊跳,問你最少跳多少次,它們才可以相遇,如果不能相遇,輸出impossble

分析:假設跳了T次以後,青蛙1的座標便是x+m* T,青蛙2的座標為y+n* T。它們能夠相遇的情況為(x+m* T)-(y+n* T)==P * L,其中P為某一個整數,變形一下

得到(n-m)* T+P * L==x-y 我們設a=(n-m),b=L,c=x-y,T=x,P=y.於是便得到ax+by==c。激動啊,這不就是上面一樣的式子嗎~

直接套用擴充套件歐幾里得函式,得到一組解x,y。由於問題是問最少跳多少次,於是只有x是我們需要的資訊。那麼再想,x是最小的嗎?

答案是可能不是!那麼如何得到最小解呢? 我們考慮x的所有解的式子: x=x0+b/d* t。x0是我們剛剛求到的,很顯然右邊是有個單調函式,當t為某一個與x正負性質相反的數時,可以得到最小的x。 令x的正負性質為正,那麼x=x0-b/d* t1 (t1==-t)。令x==0,那麼t=x0* d/b,最小的x等於x0減去t*b/d。這裡得到的x可能是負數,如果是負數,我們再為它加上一個b/d即是所求答案了!

pku 2142 the balance
題意:一個傢伙有一種天平,這種天平只有兩種重量的砝碼a和b,現在要稱出重量為c的物品,問你至少需要多少a和b,答案需要滿足a的數量加上b的數量和最小,並且他們的重量和也要最小。(兩個盤都可以放砝碼)

分析:假設a砝碼我們用了x個,b砝碼我們用了y個。那麼天平平衡時,就應該滿足ax+by==c。x,y為正時表示放在和c物品的另一邊,為負時表示放在c物品的同一邊。

於是題意就變成了求|x|+|y|的最小值了。x和y是不定式ax+by==c的解。
剛剛上面已經提到了關於x,y的所以解的同式,即
x=x0+b/d*t
y=y0-a/d*t

你是不是下意識的想要窮舉所有解,取|x|+|y|最小的?顯然是行不通的,仔細分析:|x|+|y|==|x0+b/d* t|+|y0-a/d* t|,我們規定a>b(如果a < b,我們便交換a b),從這個式子中,我們可以輕易的發現:|x0+b/d* t|是單調遞增的,|y0-a/d* t|是單調遞減的,而由於我們規定了a>b,那麼減的斜率邊要大於增的斜率,於是整個函式減少的要比增加的快,但是由於絕對值的符號的作用,最終函式還是遞增的。也就是說,函式是凹的,先減小,再增大。那麼什麼時候最小呢?很顯然是y0-a/d* t==0的時候,於是我們的最小值|x|+|y|也一定是在t=y0*d/a附近了,我是在t點左右5個點的範圍內取最小的。

求解逆元

求解模線性方程

中國的

中國剩餘定理

看到這個定理的時候,才發現,中國古代的數學家還是很厲害的。
至少我是花了一下午,才把這個定理看得還算明白。

由來

我國古代《孫子算經》中有一著名而又重要的問題:

今有物不知其數,三三數之剩二、五五數之剩三,七七數之剩二,問物幾何.答曰:二十三”
題中還介紹了它的解法:“術曰:三三數之剩二,置一百四十;五五數之剩三,置六十三;七七數之剩二,置三十;並之,得二百三十三,以二百十減之,即得.”意即:物數W=70×2+21×3+15×2-2×105=23.
接下來又給出了這類題的一般解法(餘數為一的情況):術文說:“凡三三數之剩一,則置七十;五五數之剩一,則置二十一;七七數之剩一,則置十五.一百六以上,以一百五減之,即得.”

這個問題及其解法,在世界數學史上佔有重要的地位,因此,中外數學家都尊稱為“孫子定理”或“中國剩餘定理”.

解法

為了比較清楚地瞭解“中國剩餘定理”這一名稱的由來,我們不妨先引進同餘定義:一般地,若兩個整數a、b被同一個大於1的整數m除有相同的餘數,那麼稱a、b對於模m同餘.記作: a≡b (mod m)應用同餘原理,我們把“物不知其數”問題用整數的同餘式符號表達出來,是:設N≡2 (mod 3)≡3 (mod 5)≡2 (mod 7),求最小的數N.答案是N=23.

書中問題及其解法,建立起數學模型就是:
設a、b、c為餘數, P為整數,則N≡a(mod 3)≡b(mod 5)≡c(mod 7)
的解是: N=70a+21b+15c-105P (1)
現在,我們把上述解法中的a,b,c作一分析:設M=3×5×7,則
70=2×5×7=2×(3×5×7)/3=2×M/3
21=3×7=1×(3×5×7)/5=1×M/5
15=3×7=1×(3×5×7)/7=1×M/7

因此,問題的解(1)式可以寫成: N=2×M/3a+1×M/5b+1×M/7c (2)

抽象 推廣

解法為 轉化為 同餘方程組, 並求解。
實現程式碼如下:

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 = extend_Euclid(w[i],m,x,y);  
        ret = (ret+y*m*a[i])%n;  
    }  
    return (n+ret%n)%n;  
}  

高斯消元法

線性代數的知識
待補。。。

盧卡斯定理(Lucas)

該定理可以利用模運算快速求出二項式係數C(n,r),適用於不用模運算就無法求出其結果的、具有非常大的n和r的二項式係數。

Lucas 定理:A、B是非負整數,p是質數。AB寫成p進位制:A=a[n]a[n-1]…a[0],B=b[n]b[n-1]…b[0]。

則組合數C(A,B)與C(a[n],b[n])* C(a[n-1],b[n-1])* …* C(a[0],b[0]) modp同

即:Lucas(n,m,p)=c(n%p,m%p)*Lucas(n/p,m/p,p)

模板程式碼:

LL PowMod(LL a,LL b,LL MOD){//快速冪
    LL ret=1;  
    while(b){  
        if(b&1) ret=(ret*a)%MOD;  
        a=(a*a)%MOD;  
        b>>=1;  
    }  
    return ret;  
}  
LL fac[100005];  
LL Get_Fact(LL p){//初始化
    fac[0]=1;  
    for(int i=1;i<=p;i++)  
        fac[i]=(fac[i-1]*i)%p;  
}  
LL Lucas(LL n,LL m,LL p){//Lucas 定理
    LL ret=1;  
    while(n&&m){  
        LL a=n%p,b=m%p;  
        if(a<b) return 0;  
        ret=(ret*fac[a]*PowMod(fac[b]*fac[a-b]%p,p-2,p))%p;  
        n/=p;  
        m/=p;  
    }  
    return ret;  
}

容斥原理

現在還是不太理解。。。
上個連結吧:大神眼中的容斥