数论基础

【第二讲】

第二讲我们要学习的概念有:

  • 欧几里得算法
  • 裴蜀定理
  • 扩展欧几里得算法

一、欧几里得算法

在第一讲中我们知道了 gcd 的概念,现在我们来介绍如何计算两个整数

首先我们需要知道 的一个重要性质,

证明:

(1)

的任意一个公约数,即 ,依据整除的性质:

,则

反过来,设 的任意一个公约数,即 , 同样根据整除的性质:

,则

因此,由 可得到

这也说明 也是 的公约数

综上所述, 的所有公约数与 的所有公约数完全相同,由于 是公约数集合中最大的元素,因此两者的最大公约数必然相等,即

(2)

根据带余除法的定义:

对于两个整数 ),存在唯一的整数 (商)和 (余数),使得:

其中

利用第一步的结论,我们可以反复使用

1)第一次:

2)第二次:

...

q)第 次: =

而由带余除法 可知, (即

因此,

然后我们知道 ,于是得到:

这个式子又称作 “更相减损术”

这个式子又称作 ”辗转相除法”

显然,如果不停地使用这两个式子,总能将 化成 ,于是就能得到

并且对于两个整数 ,使用 “辗转相除法” 需要的循环次数一定小于等于 "更相减损术" ,这里省略证明,留给读者自证。

Task 1: 证明上述结论

因此,我们采用计算次数更少,也就是计算复杂度更低的 “辗转相除法” 来计算 ,下面来看个例子:

例:

(1) 第一次操作

初始输入 ,依据公式,先计算

于是 ,这个时候 ,就完成了一次辗转相除操作

(2) 第二次操作

此时输入 依据公式,先计算

于是 ,这个时候 ,就完成了第二次辗转相除操作

(3) 第三次操作

此时输入 ,依据公式,先计算

于是 ,这个时候 ,就完成了第三次辗转相除操作

(4)根据 的性质, ,计算完成

所以,

让我们使用代码来实现这个过程

递归写法

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

循环写法

int gcd(int a,int b){
    while (b != 0){
        int r = a % b;
        a = b;
        b = r;
    }
    return a;
}

恭喜你!学会了 欧几里得算法 !也就是上述的 "辗转相除法"

二、裴蜀定理

又称 贝祖定理 ,内容如下:

对于任意整数 ,存在整数 ,使得以下等式成立:

其中

我们称这一组系数 贝祖系数

特殊情况

均为 质数时,即 ,存在整数 满足

简而言之,裴蜀定理表明:

两个整数的最大公约数,是这两个数所有整数线性组合中最小的正整数,并且所有能表示为 的整数,都是它们最大公约数的倍数

扩展

我们考虑数组 ,可以扩展裴蜀定理得到,存在系数数组 使得

其中

Task 2:

思考

这个式子具有什么性质?

Task 3:

思考

对于二维数组(矩阵)

另一个二维数组(矩阵) 是否也有类似性质?

三、扩展欧几里得算法

引入:

给定一个线性不定方程

已知 ,请你求出系数 的一组解

聪明的你一定发现了!诶?这个方程长得好像刚学的 裴蜀定理 啊!

让我们来回顾一下 裴蜀定理 的内容:

对于任意整数 ,存在整数 ,使得以下等式成立:

其中 ,也就是说 一定是 的倍数

所以合理猜测,如果 也是 的倍数,那么我们就知道这个方程是可解的,并且 就是贝祖系数 ;反之,如果 不是 的倍数,貌似应该解不出来整数 满足上述方程

我们来证明一下:

(1)当 的倍数时,方程 有解

,若 (即 的倍数),则存在整数 使得 ,依据裴蜀定理,存在:

将等式两边同时乘以 ,得:

此时,取 (均为整数),则 成立,因此方程有解。

实例:

方程 ,有裴蜀定理,存在 使得 ,可以解得

两边同时乘以 得到

验证成立

(2)当 不是 的倍数时,方程 无解

假设方程 存在整数解 ,由于 ,根据最大公约数的性质 ,根据整除的性质:

,则 均为整数)

因此, ,即 ,与假设相矛盾,故假设不成立,方程无解

原来如此!也就是说,我们要判断方程 是否有解,只需要判断 是否是 的倍数就行了!原来裴蜀定理中“ ”是“ 方程有解 ” 的 充要条件

那么有同学就要问了,老师老师,你还是没有讲怎么求解贝祖系数 呀!

别急,我们来整理一下知识:

对于方程 ,依据裴蜀定理知道一定存在一组贝祖系数 使得该方程成立,对于 ,我们知道可以使用”辗转相除法”,也就是 欧几里得算法 来求解,我们来模拟一下这个过程

,也就是将方程变为 ,这时候我们需要来观察一下贝祖系数的变化,容易看出,在 辗转相除的过程中,贝祖系数 也一定是在变化的( 表示经过辗转相除后变化的贝祖系数)

我们来定义两个初始状态, 当 时,我们称这时候的

时,我们称这时候的

也就是初始输入为 ,辗转之后 ,合理猜测,辗转之后的 能否用 表示出来?

于是得到方程组

容易证明存在两组贝祖系数满足这个方程,留给读者自证

Task 4

思考如何证明一定存在两组整系数 满足上述方程

这时再次进行辗转相除,得到新一轮的

我们将上述方程组带入辗转的过程中得到

首先将 赋值成

可以发现

再将 赋值成

这里我们用 表示 ,于是带入得到

化简得到

可以发现

这样我们就成功找出了每一次辗转过程中这两组贝祖系数的变化

我们使用代码来实现这个过程

C++:

在 C++ 里,y1 属于标准数学库函数,其作用是计算第一类贝塞尔函数(即 Neumann 函数)的值,所以我们这里使用 来表示两组贝祖系数(如果你不用万能头就可以正常使用

#include<bits/stdc++.h>
using namespace std;
int exgcd(int a, int b, int& x, int& y){
    x = 1; y = 0;
    int x2 = 0; int y2 = 1;
    while (b != 0){
        int q = a / b;
        tie(a, b) = make_tuple(b, a % b);
        tie(x, x2) = make_tuple(x2, x-x2 * q);
        tie(y, y2) = make_tuple(y2, y-y2 * q);
    }
    return a;
}

int main(){
	int x,y;
	cout << exgcd(84,30,x,y) << endl;
	cout << x << " " << y << endl;
	return 0;
}

Python:

def exgcd(a,b):
    x1, y1 = 1, 0
    x2, y2 = 0, 1
    while b!=0:
        q = a // b
        a, b = b, a % b
        x1, x2 = x2, x1 - x2 * q
        y1, y2 = y2, y1 - y2 * q
    return a, x1, y1

到目前为止,我们已经知道了如何求解

中的贝祖系数 ,接下来回到最开始的问题,求解方程

聪明的你一定知道怎么求了,我们来看看代码

#include<bits/stdc++.h>
using namespace std;
int exgcd(int a, int b, int& x, int& y){
    x = 1; y = 0;
    int x2 = 0; int y2 = 1;
    while (b != 0){
        int q = a / b;
        tie(a, b) = make_tuple(b, a % b);
        tie(x, x2) = make_tuple(x2, x-x2 * q);
        tie(y, y2) = make_tuple(y2, y-y2 * q);
    }
    return a;
}

void solve(int a, int b, int c, int& x,int& y){
	int d = exgcd(a,b,x,y);
	if (c % d != 0) return;  // 无解
	x *= c / d; y *= c / d;
}

int main(){
	int x,y;
	solve(84,30,12,x,y);
	cout << x << " " << y << endl;
	return 0;
}

Python:

def solve(a,b,c):
    d, x, y = exgcd(a,b)
    if c % d != 0:
        return -1  # 无解
    return x * c // d, y * c // d

恭喜你,已经学会了使用 扩展欧几里得算法 计算贝祖系数和求解线性方程!

事实上,这个函数还有更简洁的写法,来看看代码

C++:

#include <tuple>
tuple<int, int, int> exgcd(int a, int b) {
    if (b == 0) return {a, 1, 0}; // 返回{gcd, x, y}
    auto [g, x1, y1] = exgcd(b, a % b);
    int x = y1;
    int y = x1 - (a / b) * y1;
    return {g, x, y};
}

Python:

def exgcd(a, b):
    if b == 0:
        return (a, 1, 0)  # (gcd, x, y)
    g, x1, y1 = exgcd(b, a % b)
    x = y1
    y = x1 - (a // b) * y1
    return (g, x, y)

Task 5

请你给出这种写法的原理推导

让我们回过头来看看 这个方程,

例:求出 的一组解

解:

方程有解

,将两式合并得到

的解为

的解为

聪明的你一定发现了,我们在代码中实现的其实就是这样一个从 的贝祖系数 逆推回 贝祖系数 的过程