数论基础
【第二讲】
第二讲我们要学习的概念有:
- 欧几里得算法
- 裴蜀定理
- 扩展欧几里得算法
一、欧几里得算法
在第一讲中我们知道了 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
请你给出这种写法的原理推导
让我们回过头来看看 这个方程,
例:求出 的一组解
解:
,
方程有解
又 ,
,将两式合并得到
的解为
又
的解为
即
聪明的你一定发现了,我们在代码中实现的其实就是这样一个从 的贝祖系数
逆推回
贝祖系数
的过程