#数论# 欧几里德算法 、扩展欧几里德算法 、逆元求解(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) 的一组解x1,x2后,ax +by = gcd(a, b) 的其他整数解满足: x = x1 + y / gcd(p, q) * t ,y = y1 - x / gcd(p, q) * t (其中t为任意整数)

  • 对于ax +by = c 的整数解,只需将 ax +by = gcd(a, b) 的每个解乘上 c / gcd(p, q) 即可。

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。

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. 欧拉定理:

经典题目:青蛙的约会

Description
两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。
我们把这两只青蛙分别叫做青蛙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

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 <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <map>
#include <queue>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
typedef vector<LL> vec;
typedef vector<vec> 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 {
        LL ans = x * c / gcd;  //求余公式
        ans = (x % l + l) % l;  //保证解为最小正整数
        printf("%lld\n", ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Jasmineaha/article/details/80296334