算法学习笔记(1): 欧几里得算法及其扩展

算法,学习,笔记,欧几里得,及其,扩展 · 浏览次数 : 89

小编点评

**目录扩展欧几里得算法详解** **欧几里得算法** 欧几里得算法是一种递归求最大公约数的方法。通过反复调用求最大公约数,我们可以不断地缩小两个数的差值,直到得到最大公约数。 **贝祖定理** 贝祖定理是欧几里得算法的一个推论。该定理表明,如果两个数的最大公约数为 s,那么存在两个整数 s1 和 s2,使它们的最大公约数为 s。 **扩欧** 扩欧是一种基于贝祖定理的算法,用于求解形如 ax + by = c 的二元一次等式的一组合法解。扩欧的核心思路是通过不断调整两数的相对位置,逐步缩小它们,直到找到它们的最小公约数。 **贝祖系数** 贝祖系数是扩欧算法中一个重要概念。它表示两个数的最大公约数的最小非负整数系数。在扩欧过程中,我们可以通过计算两个数的最大公约数的贝祖系数来推导出它们的最小公约数。 **扩欧的代码** 以下是一些实现扩欧的代码示例: ```python def exgcd(a, b): if b == 0: return a r = exgcd(b, a % b, t, s) t -= (a / b) * s return r def gcd(a, b): return b if b == 0 else gcd(b, a % b, t, s) ``` **扩欧的优化** 扩欧算法的效率与算法的复杂性有关。当两个数的最小公约数很大时,扩欧算法可能效率低下。因此,优化扩欧算法至关重要。 **结论** 扩欧是一种基于贝祖定理的算法,可以用于求解形如 ax + by = c 的二元一次等式的一组合法解。扩欧的代码可以通过优化来提高效率,但当两个数的最小公约数很大时,扩欧算法可能效率低下。

正文

扩展欧几里得算法详解

在了解扩欧之前我们应该先了解欧几里得算法

欧几里得算法

这是一个递归求最大公约数(greatest common divisor)的方法

\[gcd(a, b) = gcd(b, a \% b) \]

可以通过一个类似的简单公式推导而来

好像叫做辗转相减法来着?

\[gcd(a, b) = gcd(b, a-b) = gcd(b, a-kb) \]

由于已知 \(a \mod b = a - b \lfloor \frac ab \rfloor\)

\(k = \lfloor \frac ab \rfloor\)则可以推导出

\[gcd(a, b) = gcd(b, a - b \lfloor \frac ab \rfloor) = gcd(b, a \% b) \]

这里给出两种代码

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

// 这种方法稍微快了那么一点点。其实也没有什么影响
int gcd(int a, int b) {
    int t;
    while (b) {
        t = a % b, a = b, b = t;
    }
    return a;
}

但是在讲扩欧之前,还需要引入一个定理

贝祖定理

\(a,b \in \mathbb{N^+}\),则 \(\exists s, t\) 满足 \(gcd(a, b) = sa + tb\)

定义:

其中,\(s,t\)称为\(a, b\)的贝祖系数,等式 \(gcd(a, b) = sa + tb\) 称为贝祖恒等式


扩展欧几里得算法

本质上就是欧几里得算法和贝祖定理的结合产生的一种算法,可以用于求出形如

\[ax + by = c \]

的二元一次等式的一组合法解(其中,\(a, b, c\)为参数)

在欧几里得算法中,核心的思路是这样的

\[gcd(a, b) = gcd(b, a \% b) = gcd(b, a - b\lfloor \frac ab \rfloor) \]

而边界条件是

\[gcd(a, b) = gcd(c, 0) = c \]

则,在边界时有

\[1 \times c + 0 \times 0 = c \]

即可知,边界时应有\(s = 1, t = 0\)

但是我们要如何回推呢?

依据贝祖定理

\[gcd(x, y) = sx + ty \]

以及

\[gcd(a, b) = gcd(b, a - b\lfloor \frac ab \rfloor) \]

令等式左右的贝祖系数为\(s_1, t_1\)\(s_2, t_2\)可以变形写出

\[\begin{aligned} s_1 a + t_1 b &= s_2 b + t_2 (a - b\lfloor \frac ab \rfloor) \\ &= t_2 a + (s_2 - t_2 \lfloor \frac ab \rfloor)b \end{aligned} \]

于是可以知晓

\[\begin{cases} s_1 = t_2 \\ t_1 = s_2 - t_2 \lfloor \frac ab \rfloor \end{cases} \]

于是,可以写出扩欧的代码

这里给出一种C-style的代码

int exgcd(int a, int b, int *s, int *t) {
    if (b == 0) {
        *s = 1, *t = 0;
        return a;
    }
    int r = exgcd(b, a % b, t, s);
    *t -= (a / b) * *s;
    return r;
}

当然,扩欧其实也是可以利用矩阵递推的

我们通过上述递推式可以将之转化为矩阵递推式

\[\begin{pmatrix} x_1 \\ y_1 \end{pmatrix} = \begin{pmatrix} 0 & 1 \\ 1 & -d_1 \end{pmatrix} \begin{pmatrix} x_2 \\ y_2 \end{pmatrix} \]

其中,\(-d_1 = \lfloor\frac ab\rfloor\)

于是,就可以一直乘下去

\[\begin{pmatrix} x \\ y \end{pmatrix} = \begin{pmatrix} 0 & 1 \\ 1 & -d_1 \end{pmatrix} \begin{pmatrix} 0 & 1 \\ 1 & -d_2 \end{pmatrix} \begin{pmatrix} 0 & 1 \\ 1 & -d_3 \end{pmatrix} \ldots \begin{pmatrix} 0 & 1 \\ 1 & -d_n \end{pmatrix} \begin{pmatrix} 1 \\ 0 \end{pmatrix} \]

那么,有了从右向左的推导,从左向右呢?

设初始矩阵为M,则需要

\[M \begin{pmatrix} 0 & 1 \\ 1 & -d_1 \end{pmatrix} = \begin{pmatrix} 0 & 1 \\ 1 & -d_1 \end{pmatrix} \]

所以

\[M = \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix} \]

于是,我们可以简单的利用矩阵乘法递推了!

但是,如果真的用矩阵模拟还不如不用,所以我们还需要一定的优化

设当前矩阵\(M\)\(\begin{pmatrix} m_1 & m_2 \\ n_1 & n_2 \end{pmatrix}\),需要乘上\(\begin{pmatrix} 0 & 1 \\ 1 & -d_k \end{pmatrix}\)

则,\(M\)变为\(\begin{pmatrix} m_2 & m_1 - m_2d_k \\ n_2 & n_1 - n_2d_k\end{pmatrix}\)

所以,就按照上面的式子写就是了(我就不提供参考了)

与算法学习笔记(1): 欧几里得算法及其扩展相似的内容:

算法学习笔记(1): 欧几里得算法及其扩展

扩展欧几里得算法详解 在了解扩欧之前我们应该先了解欧几里得算法 欧几里得算法 这是一个递归求最大公约数(greatest common divisor)的方法 $$ gcd(a, b) = gcd(b, a % b) $$ 可以通过一个类似的简单公式推导而来 好像叫做辗转相减法来着? $$ gcd(

算法学习笔记(3.1): ST算法

ST表 在RMQ(区间最值)问题中,著名的ST算法就是倍增的产物。ST算法可以在 \(O(n \log n)\) 的时间复杂度能预处理后,以 \(O(1)\) 的复杂度在线回答区间 [l, r] 内的最值。 当然,ST表不支持动态修改,如果需要动态修改,线段树是一种良好的解决方案,是 \(O(n)\

算法学习笔记(8.1): 网络最大流算法 EK, Dinic, ISAP

网络最大流算法 EK, Dinic, ISAP 详解

算法学习笔记(30):Kruskal 重构树

Kruskal 重构树 这是一种用于处理与最大/最小边权相关的一个数据结构。 其与 kruskal 做最小生成树的过程是类似的,我们考虑其过程: 按边权排序,利用并查集维护连通性,进行合并。 如果我们在合并时,新建一个节点,其权值为当前处理的边的权值,并将合并的两个节点都连向新建的节点,那么就可以得

C++算法之旅、09 力扣篇 | 常见面试笔试题(上)算法小白专用

算法学习笔记,记录容易忘记的知识点和难题。详解时空复杂度、50道常见面试笔试题,包括数组、单链表、栈、队列、字符串、哈希表、二叉树、递归、迭代、分治类型题目,均带思路与C++题解

C++算法之旅、08 基础篇 | 质数、约数

算法学习笔记,记录容易忘记的知识点和难题。试除法、分解质因数、筛质数、约数个数、约数之和、最大公约数

算法学习笔记(3): 倍增与ST算法

倍增 目录倍增查找 洛谷P2249重点变式练习快速幂ST表扩展 - 运算扩展 - 区间变式答案倍增更多的用法优化矩形查询优化建图优化 DP作者有话说 倍增,字面意思即”成倍增长“ 他与二分十分类似,都是基于”2“的划分思想 那么具体是怎么样,我们以一个例子来看 查找 洛谷P2249 依据题面,我们知

算法学习笔记(4): 并查集及其优化

# 并查集 并查集,Disjoint-Set,或者通俗一点,叫做MergeFind-Set,是一种可以动态维护若干个不重叠的集合,并支持集合之间的合并与查询的数据结构。 集体来说,并查集支持下列两个操作: - Find,查询元素所属集合 - Merge,将两个元素所属集合合并 一般来说,为了具体实现

算法学习笔记(5): 最近公共祖先(LCA)

树上最近公共祖先(LCA)三种求法:倍增,DFS+ST表,熟练剖分

算法学习笔记(6): 树链剖分

树链剖分 树链剖分是一个很神奇,但是在树上可以完成一些区间操作问题 简单来说,就是把一棵树分成一条条的链,通过维护链上的信息来维护整棵树的信息 基础知识可以参考我的另外一篇博客:算法学习笔记(5): 最近公共祖先(LCA) 这里假设你已经掌握了上述博客中的所有相关知识,并清晰了其背后的原理 性质?发