#数论# 欧几里德算法 、扩展欧几里德算法 、费马小、逆元求解(ing)

欧几里德求gcd(辗转相除法):

定理:

gcd(a, b) = gcd(b, a % b)
两个正整数a和b(a>b),它们的最大公约数等于a除以b的余数c和b之间的最大公约数

证明:

a可以表示成a = kb + r,则r = a % b;

  1. 假设d是a, b的一个公约数,则有a % d = 0,b % d = 0,由于r = a - kb,因此r % d = 0,证明充分性;
  2. 假设d 是(b, a % b)的公约数,则b % d = 0,r % d = 0,由于a = kb + r,因此a % d = 0,证明必要性;

实现代码:

int gcd(int a, int b) { //递归形式
    if(b == 0) return a;  //当 b == 0 时,(a,0)的最大公约数是a
    return gcd(b, a % b);
}

int gcd(int a, int b) { //非递归形式
    int tmp = a;
    while(b) {
        a = b;
        b = tmp % b;
    }
    return a;
}

复杂度:O( log(max(a, b)) )

扩展欧几里德算法:

用来在已知a, b求解一组x,y使得 a * x + b * y = Gcd(a, b),扩展欧几里德常用在求解横线性方程及方程组中。

核心代码:

//if(a < 0) a = -a, c = -c;  
int exgcd(int a, int b, int &x, int &y) { //a必须大于0
    if(b == 0) {
        x = 1, y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= (a / b) * x;
    return d;
}

主要应用于以下三个方面:

1. 求解不定方程:ax +by = c

  • 对于不定整数方程 ax + by = c,若 c mod gcd(a, b) = 0,则该方程存在整数解,否则不存在整数解。
  • 在找到 ax + by = gcd(a, b) 的一组解 x0,y0 后,对于 ax + by = c 的整数解,只需将 ax + by = gcd(a, b) 的每个解乘上 c / gcd(a, b) 即可。
  • ax + by = gcd(a, b) 的其他整数解满足: x1 = x0 + k * b / gcd(a, b) ,y1 = y0 - k * a / gcd(a, b) (其中 k 为任意整数)
  • 如果要保证x最小,可以令 x % (b / gcd),同理,保证y最小就需要令 y % (a / gcd)。

经典例题1:Romantic

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2669
The Sky is Sprite.
The Birds is Fly in the Sky.
The Wind is Wonderful.
Blew Throw the Trees
Trees are Shaking, Leaves are Falling.
Lovers Walk passing, and so are You.
…………………………..Write in English class by yifenfei
Girls are clever and bright. In HDU every girl like math. Every girl like to solve math problem!
Now tell you two nonnegative integer a and b. Find the nonnegative integer X and integer Y to satisfy X*a + Y*b = 1. If no such answer print “sorry” instead.

Code:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long LL;

LL exgcd(LL a, LL b, LL &x, LL &y) {
    if(b == 0) {
        x = 1, y = 0;
        return a;
    }
    LL d = exgcd(b, a % b, y, x);
    y -= (a / b) * x;
    return d;
}
int main()
{
    LL a, b;
    while(cin >> a >> b) {
        LL x, y;
        LL gcd = exgcd(a, b, x, y);
        if(1 % gcd) cout << "sorry" << endl;
        else { //由于gcd == c == 1所以直接对方程ax + by = gcd(a,b)求解即可
            while(x < 0) { //题目要求x必须大于0
                x += b / gcd;
                y -= a / gcd;
            }
            cout << x << " " << y << endl;
        }
    }
    return 0;
}

2. 求解模线性方程(线性同余方程):

同余方程 ax ≡ b (mod n) 对于未知数 x 有解,当且仅当 gcd(a,n) | b。且方程有解时,方程有 gcd(a, n) 个解,求解方程 ax ≡ b (mod n) 相当于求解方程 ax+ ny = b(x, y为整数)。
每个解之间的间隔dx = n / d。

经典例题2:青蛙的约会

题目链接:http://poj.org/problem?id=1061
两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。
我们把这两只青蛙分别叫做青蛙A和青蛙B,并且规定纬度线上东经0度处为原点,由东往西为正方向,单位长度1米,这样我们就得到了一条首尾相接的数轴。设青蛙A的出发点坐标是x,青蛙B的出发点坐标是y。青蛙A一次能跳m米,青蛙B一次能跳n米,两只青蛙跳一次所花费的时间相同。纬度线总长L米。现在要你求出它们跳了几次以后才会碰面。

Solution:

求余方程问题
假设青蛙跳了t次,根据题意列出方程:(x + t * m) % L = (y + t * n) % L ,转化为(m - n) * t + k * L = y - x,那么现在已经符合ax + by = c方程了,设a = m-n,b = L,c = y-x,由于ax + by = gcd(a, b)而不是c,因此只有当c | gcd(a,b)时才有解。
此时方程两边同时乘c / gcd(a,b),就得到 c / gcd(a, b) * (ax + by) = c 方程了

代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair PLL;
typedef vector vec;
typedef vector mat;
const int MaxN = 2e3 + 5;
const int MaxM = 1e5 + 5;
const int Mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;

LL exgcd(LL a, LL b, LL &x, LL &y) {
    if(b == 0) {
        x = 1, y = 0;
        return a;
    }
    LL d = exgcd(b, a % b, y, x);
    y -= (a / b) * x;
    return d;
}

int main()
{
    LL x, y, m, n, l;
    scanf("%lld %lld %lld %lld %lld", &x, &y, &m, &n, &l);
    LL a = m - n, b = l, c = y - x;
    if(a < 0) a = -a, c = -c;
    LL gcd = exgcd(a, b, x, y);
    if(c % gcd) printf("Impossible\n"); 
    else {
        x = x * c / gcd; 
        int base = b / gcd;
        while(x < 0) x += base; //x必须是正数
        printf("%lld\n", x % base);  //保证x最小
    }
    return 0;
}

3. 求解模的逆元:

对于正整数a,如果有 ax ≡ 1 (mod n),那么把这个同余方程中的最小正整数解x叫做a模x的逆元。
对于同余方程 ax ≡ 1(mod n), gcd(a, n) = 1 的求解就是求解方程ax + ny = 1。

逆元:

求解逆元的三种方法:

1. 费马小定理求逆元:

费马小定理:(m为素数)
推导过程:

为a在模m下的逆元(用快速幂求即可)。但有限制条件:m必须为质数,且a,m互质。

2. 拓展gcd求逆元:(a,m互质)

将a,m代入扩展gcd公式ax + by = gcd(a, b) 得出公式ax + my = gcd(a, m),令a,m互质,则gcd(a, m) = 1,得出ax +my = 1,即,此时x就是a的逆元。

3. 欧拉定理:

你可能感兴趣的:(数学)