1. 程式人生 > >歐幾里得演算法&&拓展歐幾里得演算法

歐幾里得演算法&&拓展歐幾里得演算法

參考部落格http://www.cnblogs.com/void/archive/2011/04/18/2020357.html

參考部落格http://blog.csdn.net/zhjchengfeng5/article/details/7786595

參考部落格http://blog.csdn.net/loi_dqs/article/details/49488851

歐幾里得演算法

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

拓展歐幾里得演算法

int e_gcd(int a, int b,int &x,int &y)
{
    if(b == 0)
    {
        x = 1;
        y = 0;
        return a;
    }
    int ans = e_gcd(b,a % b, x, y);
    int tem = x;
    x = y;
    y = tem - a/b*y;
    return ans;
}
注意:ans不能刪去,因為這是從後往前遞迴的。即先一直往下呼叫,一直呼叫到結束,然後此刻得到x,和y的值然後一層一層的回溯,一層一層的得到上一層的值。

拓展歐幾里得演算法演算法跟歐幾里得演算法區別在於

拓展能夠求出每一步a,b對應方程ax + by = gcd(a,b)對應的x,y

假設當前我們要處理的是求出 a 和 b的最大公約數,並求出 x 和 y 使得 a*x + b*y= gcd ,而我們已經求出了下一個狀態:b 和 a%b 的最大公約數,並且求出了一組x1 和y1 使得: b*x1 + (a%b)*y1 = gcd , 那麼這兩個相鄰的狀態之間是否存在一種關係呢?

    我們知道: a%b = a - (a/b)*b(這裡的 “/” 指的是整除,例如 5/2=2 , 1/3=0),那麼,我們可以進一步得到:

        gcd = b*x1 + (a-(a/b)*b)*y1

            = b*x1 + a*y1 – (a/b)*b*y1

            = a*y1 + b*(x1 – a/b*y1)

    對比之前我們的狀態:求一組 x 和 y 使得:a*x + b*y = gcd ,是否發現了什麼?

    這裡:

        x = y1

        y = x1 – a/b*y1

拓展歐幾里得應用

(1)求解不定方程;

(2)求解模線性方程(線性同餘方程);

(3)求解模的逆元;

(1)使用擴充套件歐幾里德演算法解決不定方程的辦法:

青蛙的約會

兩隻青蛙在網上相識了,它們聊得很開心,於是覺得很有必要見一面。它們很高興地發現它們住在同一條緯度線上,於是它們約定各自朝西跳,直到碰面為止。可是它們出發之前忘記了一件很重要的事情,既沒有問清楚對方的特徵,也沒有約定見面的具體位置。不過青蛙們都是很樂觀的,它們覺得只要一直朝著某個方向跳下去,總能碰到對方的。但是除非這兩隻青蛙在同一時間跳到同一點上,不然是永遠都不可能碰面的。為了幫助這兩隻樂觀的青蛙,你被要求寫一個程式來判斷這兩隻青蛙是否能夠碰面,會在什麼時候碰面。
我們把這兩隻青蛙分別叫做青蛙A和青蛙B,並且規定緯度線上東經0度處為原點,由東往西為正方向,單位長度1米,這樣我們就得到了一條首尾相接的數軸。設青蛙A的出發點座標是x,青蛙B的出發點座標是y。青蛙A一次能跳m米,青蛙B一次能跳n米,兩隻青蛙跳一次所花費的時間相同。緯度線總長L米。現在要你求出它們跳了幾次以後才會碰面。

Input 輸入只包括一行5個整數x,y,m,n,L,其中x≠y < 2000000000,0 < m、n < 2000000000,0 < L < 2100000000。 Output 輸出碰面所需要的跳躍次數,如果永遠不可能碰面則輸出一行"Impossible" Sample Input
1 2 3 4 5
Sample Output
4

思路:先用exgcd求ax0 + by0 = gcd(a,b)對應的x0,y0

令 d = gcd(a,b)

然後x,y分別乘以c/d得到的x1,y1滿足ax1 + by1 = c。

x1,y1就是一組解,其他的解可表示為

x = x1 - b/d*t;

y = x2  + a/d*t;

例如求x的最小正整數解

long long t = b/d;
       if(x >= 0)
        x = x % t;
       else
        x = x % t + t;

ac程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

 long long  exgcd(long long  a, long long  b, long long  &x, long long  &y)
{
    if(b == 0)
    {
        x = 1;
        y = 0;
        return a;
    }
    long long ans = exgcd( b, a % b, x, y);
    long long  tem = x;
    x = y;
    y = tem - a/b*y;
    return ans;
}
int main()
{
    long long  x, y, m, n, l;
    scanf("%lld %lld %lld %lld %lld", &x, &y, &m, &n, &l);
    long long a, b, c;
    a = n - m;//不用管其正負
    b = l;
    c = x - y;
    long long  d = exgcd(a,b,x,y);
    if(c % d != 0)
        printf("Impossible\n");
    else
    {
       x *= c/d;
       long long t = b/d;
       if(x >= 0)
        x = x % t;
       else
        x = x % t + t;
        printf("%lld\n", x);

    }
    return 0;
}


注意,此資料太大,一定使用long long 否則將會wrong answer

(2)可以轉化為(1)

(3)

滿足 a * k ≡ 1 (mod p) k 叫做 a關於p的乘法逆元。另一種表達方法是 k ≡ a-1 (mod p)

逆元在密碼學中有廣泛應用,AES密碼體系的位元組替代就是運用了逆元。(不知道說的smg)

應用:

我們知道(a+b)%p=(a%p+b%p)%p

    (a*b)%p=(a%p)*(b%p)%p

而求(a/b)%p時,可能會因為a是一個很大的數,不能直接算出來,卻又不能(a/b)%p=(a%p/b%p)%p。

但是可以通過 k ≡ b-1 (mod p) 

a / b

= a * b-1 (mod p )

= (a mod p ) * (b-1 mod p ) mod p

= (a mod p ) * (kmod p ) mod p

= a * k mod p

轉換為a*k % p 的問題,然後a是一個加減乘的式子,就可以用上面兩個取餘公式了。

    這裡,我們稱 x 是 a 關於 m 的乘法逆元

    這怎麼求?可以等價於這樣的表示式: a*x + m*y = 1

    看出什麼來了嗎?沒錯,當gcd(a , m) != 1 的時候是沒有解的這也是 a*x + b*y = c 有解的充要條件: c % gcd(a , b) == 0

轉化為(1)

int cal(int a, int m)
{
    int x, y;
    int d = exgcd(a,m,x,y);
    if(1%d != 0)
        return -1;
    x *= 1/d;
    m = abs(m);
    int ans = x % m;
    if(ans <= 0)
        ans += m;
    return ans;
}

Modular Inverse

The modular modular multiplicative inverse of an integer a modulo m is an integer x such that a-1x (modm). This is equivalent to ax≡1 (modm).

Input

There are multiple test cases. The first line of input is an integer T ≈ 2000 indicating the number of test cases.

Each test case contains two integers 0 < a ≤ 1000 and 0 < m ≤ 1000.

<h4< dd="">Output

For each test case, output the smallest positive x. If such x doesn't exist, output "Not Exist".

<h4< dd="">Sample Input
3
3 11
4 12
5 13
<h4< dd="">Sample Output
4
Not Exist
8
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;
int exgcd(int a, int b, int &x, int &y)
{
    if(!b)
    {
        x = 1;
        y = 0;
        return a;
    }
    int ans = exgcd(b, a%b, x, y);
    int tem = x;
    x = y;
    y = tem - a/b*y;
    return ans;
}
int cal(int a, int m)
{
    int x, y;
    int d = exgcd(a,m,x,y);
    if(1%d != 0)
        return -1;
    x *= 1/d;
    m = abs(m);
    int ans = x % m;
    if(ans <= 0)
        ans += m;
    return ans;
}

int main()
{
    int t;
    scanf("%d", &t);
    while(t --)
    {
        int a, m;
        scanf("%d %d", &a, &m);
        int ans = cal(a,m);
        if(ans == -1)
            printf("Not Exist\n");
        else
            printf("%d\n", ans);
    }
    return 0;
}