扩展欧几里得算法,Exgcd

前言

(exgcd)恶心欧几里得
一些问题变得简单了!

1. 引入:

有裴蜀定理:对于任意正整数 \(a,b\),总存在多组不为 \(0\) 的整数 \(x,y\),使得:

\[ax+by=\operatorname{gcd}(a,b) \]

扩展欧几里得算法(Extended Gcd,exgcd)即是来求解 \(x,y\) 的算法。

2.原理

我们首先从 \(\operatorname{gcd}\) 函数的计算过程开始思考,这个递归函数的边界是什么?
我们知道,当 \(b=0\) 时,函数要返回 \(a\)\(a\) 就是 \(a,b\) 的最大公约数。
\(x,y\) 带上,可以写成这个式子:

\[ax+0y=\operatorname{gcd}(a,b)=a \]

可以发现,此时满足这个式子的其中一组 \(x,y\) 可以取 \(x=1,y=0\)
回到一般情况,对于 \(\operatorname{gcd}(a,b) \ \ (b\ne0)\),我们向 \(\operatorname{gcd}(b,a \operatorname{mod} b)\) 递归求解。
则对于:

\[ax+by=\operatorname{gcd}(a,b) \]

可以写成:

\[bx+(a \operatorname{mod} b)y=\operatorname{gcd}(b,a \operatorname{mod} b) \]

由于 \(a \operatorname{mod} b=a-\left \lfloor \frac{a}{b} \right \rfloor b\),代入可得:

\[\begin{aligned} bx+(a \operatorname{mod} b)y&=\operatorname{gcd}(b,a \operatorname{mod} b)\\ bx+\left(a-\left \lfloor \frac{a}{b} \right \rfloor b\right)y&=\operatorname{gcd}(b,a \operatorname{mod} b)\\ bx+ay-\left \lfloor \frac{a}{b} \right \rfloor by&=\operatorname{gcd}(b,a \operatorname{mod} b)\\ ay+b\left( x-\left \lfloor \frac{a}{b} \right \rfloor y\right)&=\operatorname{gcd}(b,a \operatorname{mod} b)\\ \end{aligned}\]

对比原式,我们发现可以用从 \(\operatorname{gcd}(b,a\operatorname{mod}b)\) 处传下来的 \(x,y\) 求解出 \(\operatorname{gcd}(a,b)\)\(x,y\)
我们设从 \(\operatorname{gcd}(b,a\operatorname{mod}b)\) 处传下来的 \(x,y\)\(x_1,y_1\) 则可以表示 \(x,y\)

\[\begin{aligned} x&=y_1\\ y&=x_1-\left \lfloor \frac{a}{b} \right \rfloor y_1 \end{aligned}\]

如果我们换一下位置,即求解:

\[ay+bx=\operatorname{gcd}(a,b) \]

与上文同理可以推得:

\[ax+b\left( y-\left \lfloor \frac{a}{b} \right \rfloor x\right)=\operatorname{gcd}(b,a \operatorname{mod} b) \]

则可以表示 \(x,y\)

\[\begin{aligned} x&=x_1\ \ \ \leftarrow\text{可以不写}\\ y&=y_1-\left \lfloor \frac{a}{b} \right \rfloor x_1 \end{aligned}\]

但是在递归时需要反着传入 \(x,y\)
定义函数 \(\operatorname{exgcd}(a,b,x,y)\) 为求解满足 \(ax+by=\operatorname{gcd}(a,b)\) 的一组解 \(x,y\) 的函数,代码如下:

long long x,y; 
ll exgcd(ll a,ll b,ll &x,ll &y){//这里引用传参以保证 x,y 可以在全局范围内被更改 
	if(b==0){
		x=1;
		y=0;
		return a;//返回最简单的一种解 
	}
	ll d=exgcd(b,a%b,y,x);//由于我们推导 x,y 时将 x,y 反放,因此在递归时也要反着递归。 
	x=x;//可以不写 
	y=y-a/b*x;
	return d;
}
int gcd=exgcd(a,b,x,y);
//调用时不须将 x,y 反放 
//gcd 既是 a,b 的最大公因数 

3.求多组解

我们可以发现,满足这样的 \(x,y\) 有无数对,如果 \(x,y\) 的大小,正负等在题目中被规定,我们该如何调整 \(x,y\) 呢?
我们不妨使用因式分解中的添项法,把原式变成这样:

\[ax-\frac{ab}{\operatorname{gcd}(a,b)}k+by+\frac{ab}{\operatorname{gcd}(a,b)}k=\operatorname{gcd}(a,b)\ \ \ \ \ (k\in\mathbb{Z}) \]

可以发现,由于分母是 \(\operatorname{gcd}(a,b)\),因此这个分数 \(\frac{ab}{\operatorname{gcd}(a,b)}\) 一定不会产生浮点数。
把这个式子整理一下:

\[a\left(x-\frac{b}{\operatorname{gcd}(a,b)}k\right)+b\left(y+\frac{a}{\operatorname{gcd}(a,b)}k\right)=\operatorname{gcd}(a,b)\ \ \ \ \ (k\in\mathbb{Z}) \]

可以发现,这个式子和 \(ax+by=\operatorname{gcd}(a,b)\) 是等价的,于是可以得出 \(x,y\) 的递推求解公式,我们设从上一组 \(x,y\)\(x_1,y_1\)

\[\begin{aligned} x&=x_1-\frac{b}{\operatorname{gcd}(a,b)}\\ y&=y+\frac{a}{\operatorname{gcd}(a,b)} \end{aligned}\]

如果答案只与一个系数 \(x\)\(y\) 有关,则在递推时可以只改变答案值而不去改变另一个。如果答案与 \(x,y\) 均有关则需要同时改变 \(x,y\)
以下是将 \(x\) 调整为题目中要求的最小正整数解的代码样例:

while(x<0){
	x=x+b/gcd(a,b);
	/*
    这里的+其实是公式中k值为负数的情况
	事实上,x加y减 和 x减y加 都是可以的,只是k值正负的问题 
	*/ 
}
//当x大于0时,其最小正整数解为  
cout<<x%(b/gcd(a,b));

4.好题选讲

【好题选讲】P1516 青蛙的约会

posted @ 2025-06-05 12:24  hm2ns  阅读(22)  评论(0)    收藏  举报