贝祖定理与扩展欧几里得算法详解
贝祖定理
-
定义
给定两个整数 a a a和 b b b,一定存在整数 x x x和 y y y,使得 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b)。
换句话说,若 a x + b y = m ax+by=m ax+by=m有整数解当且仅当 m m m是 g c d ( a , b ) gcd(a,b) gcd(a,b)的倍数。
-
证明
以下 d ∣ a d|a d∣a代表 d d d是 a a a的因子
对于 a x + b y = c ax+by=c ax+by=c,设 d = g c d ( a , b ) d=gcd(a,b) d=gcd(a,b),则 d ∣ a , d ∣ b d|a,d|b d∣a,d∣b。所以我们可以得出: ∀ x , y ∈ Z \forall x,y\in Z ∀x,y∈Z, d ∣ a x , d ∣ b y d|ax,d|by d∣ax,d∣by,显然要使得之前的式子成立,则必须满足 c c c是 a a a和 b b b的公约数的倍数,又因为 x , y ∈ Z x,y\in Z x,y∈Z,所以 c c c必然是 a , b a,b a,b最大公约数的倍数,故此定理成立。
-
升级->新定义/重要推论
我们由定义可知, a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b),那么如果我们左右两边同时除以最大公约数 g c d ( a , b ) gcd(a,b) gcd(a,b),那么就得到了 p x + q y = 1 px+qy=1 px+qy=1,其中 p , q p,q p,q互质,而我们又知道一定存在一组解使得这个等式满足,所以我们可以得到:
如果两个整数 a , b a,b a,b是互质的,那么 a x + b y = 1 ax+by=1 ax+by=1一定有整数解。
-
推广
对于方程 a x + b y + c z + . . . . + n m = f ax+by+cz+....+nm=f ax+by+cz+....+nm=f(其中 a , b , c . . . n , f a,b,c...n,f a,b,c...n,f为整数)有解的充要条件就是 f f f为 g c d ( a , b , c . . . n ) gcd(a,b,c...n) gcd(a,b,c...n)的整数倍。
-
应用
由推广我想我们就已经知道了,即若给定我们一个不定式,需要我们求解 ∑ i = 0 n a i x i \sum_{i=0}^n a_ix_i ∑i=0naixi的值最小,其中最小值必须为正数,那么我们就可以直接根据贝祖定理求解得到。即 g c d ( a 1 . . . . a n ) gcd(a_1....a_n) gcd(a1....an)。
-
贝祖定理模板例题—洛谷P4529
-
AC代码
/** *@filename:贝祖定理 *@author: pursuit *@CSDNBlog:unique_pursuit *@email: 2825841950@qq.com *@created: 2021-03-28 20:54 **/ #include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 100000 + 5; const int mod = 1e9+7; int n; int a[maxn]; int gcd(int n,int m){ return m?gcd(m,n%m):n; } void solve(){ int result=0; for(int i=0;i<n;i++){ result=gcd(result,a[i]); } cout<<result<<endl; } int main() { while(cin>>n){ int temp; for(int i=0;i<n;i++){ //将负数全都更改为整数。 cin>>a[i]; if(a[i]<0){ a[i]*=-1; } } solve(); } return 0; }
扩展欧几里得算法
由上我们已知了贝祖定理的定义,那么这个算法其实就是为了解决贝祖定理,求解出整数解 ( x , y ) (x,y) (x,y)。我们知道对于欧几里得算法,它总是把 g c d ( a , b ) gcd(a,b) gcd(a,b)转化为求解 g c d ( b , a m o d b ) gcd(b,a\mod b) gcd(b,amodb),而当b变为0时返回a,此时的 a a a就等于 g c d gcd gcd。也就是说,欧几里得算法结束时变量 a a a中存放的是 g c d gcd gcd,变量 b b b中存放的是 0 0 0,因此此时显然有 a × 1 + b × 0 = g c d a × 1 + b × 0 = g c d a×1+b×0=gcd成立,此时有 x = 1 、 y = 0 x=1、y=0 x=1、y=0成立。我们先看一下证明。
-
证明
设 x = x 1 , y = y 1 x=x_1,y=y_1 x=x1,y=y1是该方程的一个解,那么
a x 1 + b y 1 = g c d ( a , b ) ( 1 ) ax_1+by_1=gcd(a,b)\space (1) ax1+by1=gcd(a,b) (1)再设 x = x 2 , y = y 2 x=x_2,y=y_2 x=x2,y=y2是方程 b x + ( a m o d b ) y = g c d ( b , a m o d b ) bx+(a\mod b)y=gcd(b,a\mod b) bx+(amodb)y=gcd(b,amodb)的一个解。即:
b x 2 + ( a m o d b ) y 2 = g c d ( b , a m o d b ) ( 2 ) bx_2+(a\mod b)y_2=gcd(b,a\mod b)\space (2) bx2+(amodb)y2=gcd(b,amodb) (2)
而根据欧几里得算法可知:
g c d ( a , b ) ≡ g c d ( b , a m o d b ) ( 3 ) gcd(a,b)\equiv gcd(b,a\mod b)\space (3) gcd(a,b)≡gcd(b,amodb) (3)
(这个非常好理解,这里不予阐述证明)。所以我们由 ( 1 ) ( 2 ) ( 3 ) (1)(2)(3) (1)(2)(3)式可以得到 a x 1 + b y 1 = b x 2 + ( a m o d b ) y 2 ( 4 ) ax_1+by_1=bx_2+(a\mod b)y_2\space (4) ax1+by1=bx2+(amodb)y2 (4),即:
a x 1 + b y 1 = b x 2 + ( a − a / b × b ) y 2 = a y 2 + b ( x 2 − a / b × y 2 ) ( 5 ) ax_1+by_1=bx_2+(a-a/b\times b)y_2=ay_2+b(x_2-a/b\times y_2)\space (5) ax1+by1=bx2+(a−a/b×b)y2=ay2+b(x2−a/b×y2) (5)
一一对应我们可得:x 1 = y 2 , y 1 = ( x 2 − a / b × y 2 ) ( 6 ) x_1=y_2,y_1=(x_2-a/b\times y_2)\space (6) x1=y2,y1=(x2−a/b×y2) (6)
通过上述步骤,我们求解得到了 x 1 , y 1 x_1,y_1 x1,y1,那么我们按此步骤叠下去,即是一个递归求解的过程。根据欧几里得算法,我们一直往下,而最终我们知道 b = 0 , a = g c d ( a , b ) b=0,a=gcd(a,b) b=0,a=gcd(a,b),此时易知 x = 1 , y = 0 x=1,y=0 x=1,y=0为最后求得方程的解,那么我们再回溯回去计算,得到最终的 x 1 , y 1 x_1,y_1 x1,y1。故得证。
-
步骤
由上证明实际上我们就是根据公式 ( 6 ) (6) (6): x 1 = y 2 , y 1 = ( x 2 − a / b × y 2 ) x_1=y_2,y_1=(x_2-a/b\times y_2) x1=y2,y1=(x2−a/b×y2)来进行推导的。我们递归去得到最后解为 x = 1 , y = 0 x=1,y=0 x=1,y=0,再反推回来得到 x 1 , y 1 x_1,y_1 x1,y1。就这么简单。
-
代码
int exGcd(int a,int b,int &x,int &y){ //利用引用最后x,y就是我们想要的值。 if(b==0){ //说明到达已知解。 x=1; y=0; return a; } int gcd=exGcd(b,a%b,x,y);//递归到最后寻找到我们已知的解。 //开始回溯,通过已知解,利用公式返回。 int temp=x;//temp=x2 x=y;//x1=y2; y=temp-a/b*y;//y1=(x2-a/b*y2)。 return gcd; }
-
升级->求解全部解
我们设新解为 x 1 + s 1 , y 1 − s 2 x_1+s_1,y_1-s2 x1+s1,y1−s2,则有 a × ( x 1 + s 1 ) + b × ( y 1 − s 2 ) = g c d a\times (x_1+s_1)+b\times(y_1-s_2)=gcd a×(x1+s1)+b×(y1−s2)=gcd成立,带入我们的公式中可得到 a s 1 = b s 2 as_1=bs_2 as1=bs2,所以 s 1 s 2 = b a \frac{s_1}{s_2}=\frac{b}{a} s2s1=ab,则我们可以对 b , a b,a b,a约分得到 s 1 , s 2 s_1,s_2 s1,s2的最小值,即为 b / g c d , a / g c d b/gcd,a/gcd b/gcd,a/gcd,故全部解为:
⟮ x = x 1 + b g c d × k , y = y 2 − a g c d × k \lgroup {x=x_1+\frac{b}{gcd}\times k,y=y_2-\frac{a}{gcd}\times k} ⟮x=x1+gcdb×k,y=y2−gcda×k,其中 k ∈ Z k\in Z k∈Z。而其中的最小非负整数解即为: ( x % b g c d + b g c d ) % b g c d (x\%\frac{b}{gcd}+\frac{b}{gcd})\%\frac{b}{gcd} (x%gcdb+gcdb)%gcdb。
-
应用一:求解方程 a x + b y = c ax+by=c ax+by=c
我们知道怎么求解 a x + b y = g c d ax+by=gcd ax+by=gcd之后,那么我们就应该要知道怎么求解 a x + b y = c ax+by=c ax+by=c了,其中 c ∈ Z c\in Z c∈Z。首先我们先求解出 a x + b y = g c d ax+by=gcd ax+by=gcd的解 ( x 1 , y 1 ) (x_1,y_1) (x1,y1),那么我们对两边同时乘以 c g c d \frac{c}{gcd} gcdc即可得到该方程的解了。对于获取全部解同上我们可以假设证明得到。
-
应用二:同余方程 a x ≡ c ( m o d m ) ax\equiv c(\mod m) ax≡c(modm)的解
我们先来解释一下什么是同余式吧:对于 a , c a,c a,c来说,如果 ( a − c ) % m = 0 (a-c)\%m=0 (a−c)%m=0,那么就说 a a a与 c c c模 m m m同余,我们记作 a ≡ c ( m o d m ) a\equiv c(\mod m) a≡c(modm),那么 m m m就成为同余式 m m m的模。
那么我们如何通过扩展欧几里得算法来解决同余方程 a x ≡ c ( m o d m ) ax\equiv c(\mod m) ax≡c(modm)的解呢?
我们可知有 ( a x − c ) % m = 0 (ax-c)\%m=0 (ax−c)%m=0成立,那么设存在整数 y y y使得 a x − c = m y ax-c=my ax−c=my,我们通过移项并令 y = − y y=-y y=−y,即得到 a x + m y = c ax+my=c ax+my=c,这个不就是我们上面的方程吗?那么我们知道当 c % g c d ( a , m ) = = 0 c\%gcd(a,m)==0 c%gcd(a,m)==0时才有解,那么我们就可以根据这个判断是否有解。而对于解的形式我们可以先求出一组解,然后通过全解公式得到 x ′ = x + m g c d ( a , m ) × k , y ′ = y − m g c d ( a , m ) × k x'=x+\frac{m}{gcd(a,m)}\times k,y'=y-\frac{m}{gcd(a,m)}\times k x′=x+gcd(a,m)m×k,y′=y−gcd(a,m)m×k( k ∈ Z k\in Z k∈Z )。那么此问题易得。我们整理如下:
- 若 c % g c d ( a , m ) c\%gcd(a,m) c%gcd(a,m) ≠ 0 ≠0 =0,那么同余方程无解,
- 否则,通解为: x ′ = x + m g c d ( a , m ) × k x'=x+\frac{m}{gcd(a,m)}\times k x′=x+gcd(a,m)m×k。最小正整数解为: ( x % m g c d ( a , m ) + m g c d ( a , m ) ) % m g c d ( a , m ) (x\%\frac{m}{gcd(a,m)}+\frac{m}{gcd(a,m)})\%\frac{m}{gcd(a,m)} (x%gcd(a,m)m+gcd(a,m)m)%gcd(a,m)m
-
应用二例题:洛谷P1082同余方程
-
AC代码
/** *@filename:同余方程 *@author: pursuit *@CSDNBlog:unique_pursuit *@email: 2825841950@qq.com *@created: 2021-03-29 12:31 **/ #include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 100000 + 5; const int mod = 1e9+7; ll a,b; ll x,y;//存储解。 ll exGcd(ll a,ll b,ll &x,ll &y){ if(b==0){ x=1; y=0; return a; } int gcd=exGcd(b,a%b,x,y); int temp=x;//temp=x2; x=y;//x1=y2; y=temp-a/b*y;//y1=x2-a/b*y2; return gcd; } void solve(){ int gcd=exGcd(a,b,x,y); //此时我们得到了x,y。利用公式求解最小值。 cout<<((x%(b/gcd)+(b/gcd))%(b/gcd))<<endl; } int main() { while(cin>>a>>b){ solve(); } return 0; }
-
应用三:逆元的求解
我们首先解释一下什么是逆元:
假设 a 、 b 、 m a、b、m a、b、m是整数, m > 1 m>1 m>1,且有 a b ≡ 1 ( m o d m ) a b ≡ 1 (\mod m) ab≡1(modm)成立,
那么就说 a a a和 b b b互为模m的逆元,一般记作 a ≡ 1 b a\equiv \frac{1}{b} a≡b1或者 b ≡ 1 a b\equiv \frac{1}{a} b≡a1。通俗的讲就是,如果两个整数的乘积模m后等于1,那么就称它们互为m的逆元。
而逆元的作用就是可以在取模的时候等价代换成分数,将除法运算变成乘法运算。即若对于 ( x / y ) % m (x/y)\%m (x/y)%m,我们没有除法的取模分配,这个时候我们就可以求出 y y y的逆元(假设求得为 q q q),那么我们就可以转换为 ( x ∗ q ) % m = ( x % m ) ∗ ( q % m ) (x*q)\%m=(x\%m)*(q\%m) (x∗q)%m=(x%m)∗(q%m)。
那么即要求我们求解同余式方程 a x ≡ 1 ( m o d m ) ax\equiv 1(\mod m) ax≡1(modm),这个我们就可以参考应用二来解决了,最后代入到实际引用中,同样,对于逆元是要求越小越好,我们可以求出最小正整数解。
当然对于逆元的求解我们还可以使用费马小定理,若对这个不太清楚的还请阅读费马小定理。
-
应用三模板例题:HDU1576
-
扩展欧几里得算法
/** *@filename:B *@author: pursuit *@CSDNBlog:unique_pursuit *@email: 2825841950@qq.com *@created: 2021-03-29 17:24 **/ #include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 100000 + 5; const int mod = 1e9+7; int exGcd(int a,int b,int &x,int &y){ if(b==0){ x=1; y=0; return a; } int gcd=exGcd(b,a%b,x,y); int temp=x;//temp=x2; x=y;//x1=y2; y=temp-a/b*y;//y2=x2-a/b*y1; return gcd; } int n,b; void solve(){ int x,y; int gcd=exGcd(b,9973,x,y); x=(x%(9973/gcd)+9973/gcd)%(9973/gcd); cout<<(n*(x%9973))%9973<<endl; } int main() { int t; while(cin>>t){ while(t--){ cin>>n>>b; solve(); } } return 0; }
-
费马小定理
/** *@filename:B *@author: pursuit *@CSDNBlog:unique_pursuit *@email: 2825841950@qq.com *@created: 2021-03-29 17:24 **/ #include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 100000 + 5; const ll mod = 9973; ll n,b; ll quick_pow(ll a,ll b){ ll ans=1; b%=mod; while(b){ if(b&1)ans=ans*a%mod; a=a*a%mod; b>>=1; } return ans; } void solve(){ ll res=quick_pow(b,mod-2); cout<<res*n%mod<<endl; } int main() { int t; while(cin>>t){ while(t--){ cin>>n>>b; solve(); } } return 0; }
-
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)