解二元一次方程
首先这一切的一切都要从一个二元一次方程说起,假如我们要求:a * x + b * y = m 这个方程的一组整数解,a、b、m 都是整数且已知,那我们如何得知x、y是否有整数解,且如何使用编程求得???
为了解决这些问题就诞生了如下思考:
裴蜀定理:对任意两个整数a、b,设 d = gcd(a , b) 。那么关于未知数 x 和 y 的线性丢番图方程(称为裴蜀等式):a * x + b * y = m 有整数解(x0,y0)当且仅当 m 是 d 的倍数 。裴蜀等式有解时必然有无穷多个解。
推论:若 a * x + b * y = 1 成立,则a,b 互质,即 gcd(a , b) = 1。
此时我们发现这个定理给了我们判断一个二元一次方程是否有整数解的方法,接下来就是如何求得一组整数解。
欧几里得算法: 又名辗转相除法,是求两个数的最大公因数的方法,编程中通常使用递归实现。
int gcd(int a,int b){
return b==0 ? a : gcd(b , a%b);
}
扩展欧几里得算法: 讲到这你就会发现,裴蜀定理也中有gcd(a,b) ,那就和欧几里得自然而然的联系起来了。所以我们改进了计算两个数最大公因数的递归的过程,使其在给定a,b 时能解出 a∗x + b∗y = gcd(a , b) 这个二元一次方程中的 x 和 y,但这个算法得到的是裴蜀定理中的一个特解,也就是说得到的是当 m = gcd( a, b ) 时的x、y,那如果 m不等于 gcd(a , b) 呢,所以我们把这时的 x 和 y 都扩大 m / gcd(a, b) 倍就好了。🆗求解结束,至此我们解决了开头提出的问题,得到了一组整数解。(具体改进的证明过程可自行搜索)。
接下来我们再延伸一些其他发现:
线性同余方程: a ∗ x ≡ b (mod m) ,数论中,线性同余方程是最基本的同余方程,“线性”表示方程的未知数次数是一次。把这个方程变形化简后可以变成:存在整数 y,使得:a * x = m * y + b ,再变换后就成了:a * x + m* Y = b ,然后你就明白了,解线性同余方程,原来是和解二元一次方程一样的 (Orz) 。同样的 x 也会有多个解,在模 m 的完全剩余系 {0,1,…,m-1} 中,恰有 gcd(a , m) 个解。
逆元(今天的主角儿):
什么是逆元?
逆元的定义: 对于正整数 a 和 m,如果有 a * x ≡ 1 (mod m),那么把这个同余方程中 x 的最小正整数解叫做 a 模 m 的逆元
逆元的含义: 模 m 意义下,x 是 a 的逆元,那么除以 a 相当于乘以 x ,也就是求:(a / b) % m =a * x %m
证明如下:
设 b * k = 1 (mod p) 则 k 就是 b 的逆元(即 k = inv[b] ) ;
所以 b = (m * p +1) / k ,
那么 (a / b) % m = a / ( (m * p +1) / k ) mod p = a * k / (m * p + 1) mod p = a * k (mod p)
逆元的作用: 当计算 a / b(mod m) 时,如果 b 要比 a 大很多,此时会爆double精度,那么 a/b 再求余m的结果必然会比实际值偏小,这时候我们就可以做出逆元的转换变成乘法来得到准确结果。
1、扩展欧几里得算法求解逆元: 对于正整数 a 和 m,如果有 a * x ≡ 1 (mod m),前面我们把线性同余转化为二元一次方程,讨论是否有解的问题,此时 b = 1,也就是 a * x + m * Y = 1 ,其中 x 有整数解的充分必要条件是 gcd(a , m) = 1,即a与m得互质,若此条件成立,则可以使用此方法。求解后,得出来的 x 就是 a 在mod m 意义下的逆元。
2、费马小定理求解逆元: 此定理是数论中的一个重要定理,在1636年提出。如果 p是一个质数,而整数 a 不是 p 的倍数 (gcd(a , p) = 1) ,则有a^(p-1)≡1(mod p)。可以变化为 a * a ^ (p-2) ≡ 1 (mod p) ,则此时 a 模 m 的逆元就是 a^(p-2) 。
然后我们发现前面两种求逆元的方法都有局限性,都有互质的要求,下面给出更广泛的求法:
3、数学公式法:
所以,若 b | a ,则 ( a / b )mod m = (a mod (b*m) ) / b 。
4、公式递推( O(n) ):
证明过程
1、exgcd 代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int exgcd(int a,int b,int &x,int &y)
{
if(!b){
x = 1 , y=0;
return a;
}
int d = exgcd(b, a%b, y, x);
y -= a / b * x;
return d;
}
int main()
{
int T;
cin>>T;
while(T--){
int a,b,m;
cin>>a>>b>>m;
int x,y;
int d = exgcd(a,m,x,y); // d 返回的为最大公约数;
printf("%d\n",(x+m) % m ); // x 即为逆元,要求为正数。
}
return 0;
}