辗转相除法(欧几里得算法):
求最大公约数 ( a , b ) , a > b (a, b), a > b ( a , b ) , a > b 。
有:
a ÷ b = q … r a = b q + r r = a − b q a \div b = q \ldots r\\
a = bq + r \\
r = a - bq
a ÷ b = q … r a = b q + r r = a − b q
辗转相除法指出: gcd ( a , b ) = gcd ( b , r ) \gcd(a, b) = \gcd(b, r) g cd( a , b ) = g cd( b , r )
我们设
d d d 为 a a a 和 b b b 的任意一个公约数。
以及
m = a ÷ d , n = b ÷ d m = a \div d,\ n = b \div d
m = a ÷ d , n = b ÷ d
那么:
a = d m , b = d n r = a − b q = d m − ( d n ) q = d ( m − n q ) a = dm,\ b = dn\\
\begin{align*}
r &= a - bq\\
&= dm - (dn)q\\
&= d(m - nq)
\end{align*}
a = d m , b = d n r = a − b q = d m − ( d n ) q = d ( m − n q )
因为 r = d ( m − n q ) r = d(m - nq) r = d ( m − n q ) 。
所以我们可以得出,如果 a a a 和 b b b 有任意一个公因数 d d d ,这个公因数就一定会是 r r r 和 b b b 的公因数(r = 0 r=0 r = 0 的情况除外,如果 r = 0 r=0 r = 0 ,那么最大公约数就是 a a a 了)。
也就是如果我们设 A B AB A B 为 a a a 和 b b b 的公约数集合, R B RB RB 为 r r r 和 b b b 的公因数集合,那么 A B ∈ R B if r ≠ 0 AB \in RB \ \operatorname{if} \ r \ne 0 A B ∈ RB if r = 0 。
但是这还不足以证明 gcd ( a , b ) = gcd ( b , r ) \gcd(a, b) = \gcd(b, r) g cd( a , b ) = g cd( b , r ) ,因为有可能 R B RB RB 中有比 gcd ( a , b ) \gcd(a, b) g cd( a , b ) 更大的数字。
但如果我们证明了 R B ∈ A B RB \in AB RB ∈ A B ,我们就可以证明 A B = R B AB = RB A B = RB ,这样 R B RB RB 中就绝对没有比 gcd ( a , b ) \gcd(a, b) g cd( a , b ) 更大的数了。
我们设 e e e 为 R B RB RB 中的任意一个数字。
那么有
b = m e r = n e b = me\\
r = ne
b = m e r = n e
再回到这个式子上,带入 b b b 和 r r r 。
a = b q + r a = ( m e ) q + ( n e ) r a = ( m q + n r ) e \begin{align*}
a &= bq + r\\
a &= (me)q + (ne)r\\
a &= (mq + nr)e
\end{align*}
a a a = b q + r = ( m e ) q + ( n e ) r = ( m q + n r ) e
说明,e ∣ a e \mid a e ∣ a ,或者说 R B RB RB 中的任意一个数字 e e e 也在 A B AB A B 中。也就是 R B ∈ A B RB \in AB RB ∈ A B 。
所以 gcd ( a , b ) = gcd ( b , r ) \gcd(a, b) = \gcd(b, r) g cd( a , b ) = g cd( b , r ) 。
把辗转相除法写成程序的话就是下面这样,非常的简洁:
1 2 3 4 5 6 int gcd (int a, int b) { if (b) return gcd (b, a % b); else return a; }
参考资料:
https://www.bilibili.com/video/BV19r4y127fu?spm_id_from=333.880.my_history.page.click&vd_source=4de003ee9a3815aedd7d0cb2c7a12d14
https://www.bilibili.com/video/BV1my4y1z7Zn?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=4de003ee9a3815aedd7d0cb2c7a12d14 ’
https://www.cnblogs.com/zjp-shadow/p/9267675.html#扩展欧几里得
扩展欧几里得 (exgcd)
在扩展欧几里得算法中,我们尝试找出方程:
a x + b y = gcd ( a , b ) ax + by = \gcd(a, b)
a x + b y = g cd( a , b )
的一个解。
下面是一个辗转相除法计算过程的例子,它计算的是 gcd ( 1180 , 482 ) \gcd(1180, 482) g cd( 1180 , 482 ) ,最后的结果是 2 2 2 :
gcd ( a , b ) = gcd ( b , r ) a = b q + r 1180 = 482 ( 2 ) + 216 482 = 216 ( 2 ) + 50 216 = 50 ( 4 ) + 16 50 = 16 ( 3 ) + 2 16 = 2 ( 8 ) + 0 \begin{align*}
\gcd(a, b) &= \gcd(b, r)\\
a &= bq + r\\
1180 &= 482(2) + 216\\
482 &= 216(2) + 50\\
216 &= 50(4) + 16\\
50 &= 16(3) + 2 \\
16 &= 2(8) + 0\\
\end{align*}
g cd( a , b ) a 1180 482 216 50 16 = g cd( b , r ) = b q + r = 482 ( 2 ) + 216 = 216 ( 2 ) + 50 = 50 ( 4 ) + 16 = 16 ( 3 ) + 2 = 2 ( 8 ) + 0
我们可以从这个过程中推出 2 = 1180 x + 482 y 2 = 1180x + 482y 2 = 1180 x + 482 y 的一组解。
首先从过程的倒数第二步,也就是 50 = 16 ( 3 ) + 2 50 = 16(3) + 2 50 = 16 ( 3 ) + 2 开始看。把这个式子变换一下,变成:
2 = 50 + 16 ( − 3 ) 2 = 50 + 16(-3)
2 = 50 + 16 ( − 3 )
按照相同的方法,也就是 a = b q + r → r = a + b ( − q ) a = bq + r \to r = a + b(-q) a = b q + r → r = a + b ( − q ) 变换辗转相除法的前面几个步骤,可以得到:
216 = 50 ( 4 ) + 16 → 16 = 216 + 50 ( − 4 ) 482 = 216 ( 2 ) + 50 → 50 = 482 + 216 ( − 2 ) 1180 = 482 ( 2 ) + 216 → 216 = 1180 + 482 ( − 2 ) \begin{align*}
216 = 50(4) + 16 \ &\to \ 16 = 216 + 50(-4)\\
482 = 216(2) + 50 \ &\to \ 50 = 482 + 216(-2)\\
1180 = 482(2) + 216 \ &\to \ 216 = 1180 + 482(-2)
\end{align*}
216 = 50 ( 4 ) + 16 482 = 216 ( 2 ) + 50 1180 = 482 ( 2 ) + 216 → 16 = 216 + 50 ( − 4 ) → 50 = 482 + 216 ( − 2 ) → 216 = 1180 + 482 ( − 2 )
再把这些式子带到 2 = 50 + 16 ( − 3 ) 2 = 50 + 16(-3) 2 = 50 + 16 ( − 3 ) 中,可以发现,我们能把式中的 16 16 16 替换成 216 216 216 和 50 ( − 4 ) 50(-4) 50 ( − 4 ) 的和。
现在这个式子就变成 2 = 216 x + 50 y 2 = 216x + 50y 2 = 216 x + 50 y 了,其中 x = − 3 , y = 13 x=-3,\ y=13 x = − 3 , y = 13 。
进一步替换式中的 50 50 50 为 482 482 482 和 216 ( − 2 ) 216(-2) 216 ( − 2 ) 的和,式子也就成了 2 = 482 x + 216 y 2 = 482x + 216y 2 = 482 x + 216 y 。
而这个 216 216 216 被替换为 1180 1180 1180 和 482 ( − 2 ) 482(-2) 482 ( − 2 ) 的和,最终的式子就成为了。
2 = 1180 x + 482 y 2 = 1180x + 482y
2 = 1180 x + 482 y
其中 x = − 29 , y = 71 x=-29,\ y=71 x = − 29 , y = 71
这正是我们想要的答案。
看的出来 exgcd 有点像是辗转相除法的逆向过程。它利用辗转相除法的计算过程,推出了 a x + b y = gcd ( a , b ) ax + by = \gcd(a, b) a x + b y = g cd( a , b ) 的一个解。
现在我们来尝试来推广一下刚刚观察到的规律。首先我们想求的是:
a x + b y = gcd ( a , b ) ax + by = \gcd(a, b)\\
a x + b y = g cd( a , b )
因为 gcd ( a , b ) = gcd ( b , a m o d b ) \gcd(a, b) = \gcd(b, a \bmod b) g cd( a , b ) = g cd( b , a mod b ) 。而 gcd ( b , a m o d b ) \gcd(b, a \bmod b) g cd( b , a mod b ) 也可以被写成 a x + b y ax + by a x + b y 的形式,就是。
gcd ( b , a m o d b ) = b x 2 + ( a m o d b ) y 2 \begin{align*}
\gcd(b, a \bmod b) = bx_2 + (a \bmod b)y_2
\end{align*}
g cd( b , a mod b ) = b x 2 + ( a mod b ) y 2
注意虽然这里的 gcd ( b , a m o d b ) \gcd(b, a \bmod b) g cd( b , a mod b ) 是和 gcd ( a , b ) \gcd(a, b) g cd( a , b ) 一样的。也就是。
a x + b y = b x 2 + ( a m o d b ) y 2 ax + by = bx_2 + (a \bmod b)y_2
a x + b y = b x 2 + ( a mod b ) y 2
这两个式子的形式是一样的,都是 a x + b y = gcd ( a , b ) ax + by = \gcd(a, b) a x + b y = g cd( a , b ) ,但是它们中的 a a a 和 b b b 不一样,所以解出来的 x x x 和 y y y 是不一样的。假设我们已经知道了。解出来的 x 2 x_2 x 2 和 y 2 y_2 y 2 ,那么只要知道如何从 x 2 x_2 x 2 和 y 2 y_2 y 2 中计算出来 x x x 和 y y y ,就能递归的求解 x x x 和 y y y 了,
而我们可以化简 b x 2 + ( a m o d b ) y 2 bx_2 + (a \bmod b)y_2 b x 2 + ( a mod b ) y 2 。
a x + b y = b x 2 + ( a m o d b ) y 2 = b x 2 + ( a − ⌊ a b ⌋ b ) y 2 = a y 2 + b x 2 − ⌊ a b ⌋ b y 2 = a y 2 + b ( x 2 − ⌊ a b ⌋ y 2 ) \begin{align*}
ax + by &= bx_2 + (a \bmod b)y_2\\
&= bx_2 + (a - \lfloor{\frac{a}{b}}\rfloor b)y_2 \\
&= ay_2 + bx_2 - \lfloor{\frac{a}{b}}\rfloor by_2 \\
&= ay_2 + b(x_2 - \lfloor{\frac{a}{b}}\rfloor y_2)
\end{align*}
a x + b y = b x 2 + ( a mod b ) y 2 = b x 2 + ( a − ⌊ b a ⌋ b ) y 2 = a y 2 + b x 2 − ⌊ b a ⌋ b y 2 = a y 2 + b ( x 2 − ⌊ b a ⌋ y 2 )
可以发现,假设我们已经求出了 b x 2 + ( a m o d b ) y 2 = gcd ( b , a m o d b ) bx_2 + (a \bmod b)y_2 = \gcd(b, a \bmod b) b x 2 + ( a mod b ) y 2 = g cd( b , a mod b ) 的解 ( x 2 , y 2 ) (x_2, y_2) ( x 2 , y 2 ) ,那么原式 a x + b y = gcd ( a , b ) ax + by = \gcd(a, b) a x + b y = g cd( a , b ) 中的 x = y 2 x = y_2 x = y 2 ,而 y = ( x 2 − ⌊ a b ⌋ y 2 ) y = (x_2 - \lfloor{\frac{a}{b}}\rfloor y_2) y = ( x 2 − ⌊ b a ⌋ y 2 ) 。这样我们就可以递归的求解了。
而边界条件和普通辗转相除法相似,是 b = 0 b = 0 b = 0 。那么。
a x + b y = gcd ( a , b ) a x + ( 0 ) y = a x = 1 \begin{align*}
ax + by &= \gcd(a, b)\\
ax + (0)y &= a\\
x &= 1\\
\end{align*}
a x + b y a x + ( 0 ) y x = g cd( a , b ) = a = 1
虽然这里的 y y y 随便怎么搞都可以,但是我们一般返回的是 0 0 0 。
下面是代码(用的是 c++20 的标准):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 template <typename T>concept Integral = std::is_integral<T>::value;template <Integral T>tuple<T, T, T> ex_gcd (T a, T b) { if (b == 0 ) { return {a, 1 , 0 }; } auto [gcd, x2, y2] = ex_gcd (b, a % b); T x = y2; T y = x2 - (a / b) * y2; return {gcd, x, y}; }
参考资料:
https://zhuanlan.zhihu.com/p/86561431
https://www.cnblogs.com/zjp-shadow/p/9267675.html#扩展欧几里得
乘法逆元
a m o d p a\bmod p a mod p 的乘法逆元定义为 a x ≡ 1 ( m o d b ) ax \equiv 1 \pmod b a x ≡ 1 ( mod b ) 的解 x x x 。
乘法逆元有点像是模意义下的相反数。
exgcd
在 a a a 和 b b b 互质的情况下,我们可以使用 exgcd 解决这个问题。
因为 a a a 和 b b b 互质,所以:
gcd ( a , b ) = 1 \gcd(a, b) = 1
g cd( a , b ) = 1
那么扩展欧几里得可以解决:
a x + b y = 1 ax + by = 1
a x + b y = 1
我们稍微把 a x ≡ 1 ( m o d b ) ax \equiv 1 \pmod b a x ≡ 1 ( mod b ) 变一下形:
a x ≡ 1 ( m o d b ) a x m o d b = 1 a x − b k = 1 \begin{align*}
ax &\equiv 1 \pmod b\\
ax \bmod b &= 1\\
ax - bk &= 1 \\
\end{align*}
a x a x mod b a x − bk ≡ 1 ( mod b ) = 1 = 1
如果我们让 y = − k y = -k y = − k ,那么就得到了。
a x + b y = 1 ax + by = 1
a x + b y = 1
但是 a x + b y = 1 ax + by = 1 a x + b y = 1 中的 x x x 和 y y y 中可能有一个是负数,如果 y y y 是负数,那没问题,但如果 x x x 是负数,我们得到的答案就不是所有可行的 x x x 中最小的正整数了。
观察 a x + b y = 1 ax + by = 1 a x + b y = 1 这个式子,我们可以给 x x x 加 b b b 的倍数,让式子变成 a ( x + b n ) + b ( y + a n ) = 1 a(x + bn) + b(y + an) = 1 a ( x + bn ) + b ( y + an ) = 1 (注意 b b b 是负数,a b n abn abn 会被抵消掉)。这样就可以在不改变 a x + b y = 1 ax + by = 1 a x + b y = 1 的情况下把 x x x 变成正数。
所以我们可以这么写:
我们假设 x x x 是一个负数。
注意这里第一个的 x % b
的作用是先给 x x x 加上一些 b b b ,让它变成符合条件的最大的负数。比如 b b b 是 13 13 13 ,x x x 是 − 25 -25 − 25 。我们让 x = x % b
,x x x 就变成了 − 12 -12 − 12 ,相当于把 x x x 加上了 13 13 13 。
后面的 +b
就是让这个符合条件的最大负数变成符合条件的最小正数。比如 x + b = − 12 + 12 = 1 x + b = -12 + 12 = 1 x + b = − 12 + 12 = 1 。那么最后的这个 % b
有什么用呢?
这个是为了应对 x x x 为正数的情况,我们可以通过给 x x x 减去一些 b b b ,让其变成符合条件的最小正数。
然后对于乘法逆元的模板题 ,可以写出如下的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int n, p;template <typename T>concept Integral = std::is_integral<T>::value;template <Integral T>tuple<T, T, T> ex_gcd (T a, T b) { if (b == 0 ) { return {a, 1 , 0 }; } auto [gcd, x2, y2] = ex_gcd (b, a % b); T x = y2; T y = x2 - (a / b) * y2; return {gcd, x, y}; } int main () { cin>>n>>p; for (int i = 1 ; i <= n; i++){ auto [gcd, x, y] = ex_gcd (i, p); x = (x % p + p) % p; cout<<x<<endl; } }
需要注意的是,因为 3 e 6 3e6 3 e 6 的数据规模和 500 m s 500ms 500 m s 的时限,用 n log p n\log p n log p 的算法是过不了的,需要用下面讲的线性算法。
参考资料:
https://www.cnblogs.com/zjp-shadow/p/7773566.html
https://zhuanlan.zhihu.com/p/86561431
线性递推
线性递推的方法可以让我们在 O ( n ) \operatorname{O}(n) O ( n ) 的时间内求出 1 ∼ n 1\sim n 1 ∼ n 中所有整数在模质数 p p p 下的乘法逆元。
注意如果要求出 1 ∼ n 1 \sim n 1 ∼ n 的范围中的所有整数,p p p 必须是质数,因为 1 sin n 1\sin n 1 sin n 的这个区间中可能有很多非质数,要保证 1 ∼ n 1\sim n 1 ∼ n 中的数和 p p p 互质,只能确保 p p p 为质数。
因为这是一个递推算法,所以需要有初始条件。
不难发现 1 1 1 在模任何整数意义下的逆元都是 1 1 1 本身(因为 1 × 1 = 1 1 \times 1 = 1 1 × 1 = 1 )。所以我们有了初始条件。
假设我们现在已经递推到了数字 i i i 。
设 p ÷ i = k … r p \div i = k \ldots r p ÷ i = k … r ,那么:
p = k i + r p = ki + r
p = ki + r
转化为同余方程可以得到:
k i + r m o d p = 0 k i + r ≡ 0 ( m o d p ) ki + r \bmod p = 0\\
ki + r \equiv 0 \pmod p
ki + r mod p = 0 ki + r ≡ 0 ( mod p )
记 i − 1 , r − 1 i^{-1},\ r^{-1} i − 1 , r − 1 分别为 i , r i,\ r i , r 在模 p p p 意义下的乘法逆元。把 i − 1 , r − 1 i^{-1},\ r^{-1} i − 1 , r − 1 同时乘到同余式,可得:
i − 1 r − 1 ( k i + r ) ≡ 0 i − 1 r − 1 ( m o d p ) 注: i − 1 r − 1 因为 × 0 被化简了 i^{-1}r^{-1}(ki + r) \equiv 0\ \cancel{i^{-1}r^{-1}} \pmod p \\
\footnotesize{注:i^{-1}r^{-1} 因为 \times 0 被化简了}
i − 1 r − 1 ( ki + r ) ≡ 0 i − 1 r − 1 ( mod p ) 注: i − 1 r − 1 因为 × 0 被化简了
展开,得:
i − 1 r − 1 ( k i + r ) ≡ 0 ( m o d p ) i − 1 r − 1 k i + i − 1 r − 1 r ≡ 0 ( m o d p ) k r − 1 + i − 1 ≡ 0 ( m o d p ) 注:因 i − 1 是 i 在模 p 意义下的逆元, i − 1 × i ≡ 1 ( m o d p ) 对于 r 和 r − 1 也是 \begin{align*}
i^{-1}r^{-1}(ki + r) &\equiv 0 \pmod p\\
i^{-1}r^{-1}ki + i^{-1}r^{-1}r &\equiv 0 \pmod p\\
kr^{-1} + i^{-1} &\equiv 0 \pmod p\\
\footnotesize 注: 因 i^{-1} 是 i 在模 p 意义下的逆元,i^{-1}\times i &\equiv 1 \footnotesize {\pmod p\ 对于 r 和 r^{-1} 也是}
\end{align*}
i − 1 r − 1 ( ki + r ) i − 1 r − 1 ki + i − 1 r − 1 r k r − 1 + i − 1 注:因 i − 1 是 i 在模 p 意义下的逆元, i − 1 × i ≡ 0 ( mod p ) ≡ 0 ( mod p ) ≡ 0 ( mod p ) ≡ 1 ( mod p ) 对于 r 和 r − 1 也是
移项,得:
i − 1 ≡ − k r − 1 ( m o d p ) i^{-1} \equiv -kr^{-1} \pmod p
i − 1 ≡ − k r − 1 ( mod p )
因为 p ÷ i = k … r p \div i = k \ldots r p ÷ i = k … r ,所以 k = ⌊ p i ⌋ k = \lfloor \frac{p}{i} \rfloor k = ⌊ i p ⌋ 。而 r = p m o d i r = p \bmod i r = p mod i 。
带入 i − 1 ≡ − k r − 1 ( m o d p ) i^{-1} \equiv -kr^{-1} \pmod p i − 1 ≡ − k r − 1 ( mod p ) 后,得:
i − 1 ≡ − ⌊ p i ⌋ × ( p m o d i ) − 1 ( m o d p ) i^{-1} \equiv -\lfloor \frac{p}{i} \rfloor \times (p \bmod i)^{-1} \pmod p
i − 1 ≡ − ⌊ i p ⌋ × ( p mod i ) − 1 ( mod p )
考虑前面用 exgcd 解的时候提到的 a x − b k = 1 ax - bk = 1 a x − bk = 1 ,其中的 x x x 可能是负数。所以我们用了这个方法让他变成最小的正整数解。
现在的 i − 1 i^{-1} i − 1 也是一样的,我们可以将其写成 a i − p k = 1 ai - pk = 1 ai − p k = 1 的形式。并且这个 i − 1 i^{-1} i − 1 也是负数,于是就可以用相同的方法确保我们得到的 i − 1 i^{-1} i − 1 是最小的正整数解。然后就可以写出如下的模板题代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ll inv[MAXN]; template <typename T>concept Int_t = is_integral<T>::value;template <Int_t T>inline T mod_norm (T val, T m) { return (val % m + m) % m; } int main () { ios::sync_with_stdio (0 ); cin.tie (0 ); ll n, p; cin >> n >> p; inv[1 ] = 1 ; cout << inv[1 ] <<'\n' ; for (int i = 2 ; i <= n; i++) { inv[i] = mod_norm (-p / i * inv[p % i] % p, p); cout << inv[i] <<'\n' ; } }
参考资料:
https://zhuanlan.zhihu.com/p/86561431