数论——同余问题全家桶2 不定方程和同余方程
数论——同余问题全家桶2 不定方程和同余方程
- 不定方程
- 裴蜀定理
- 裴蜀定理OJ示例
- 【模板】裴蜀定理 - 洛谷
- 扩展欧几里得算法
- 扩欧求二元一次不定方程的解
- 扩欧定理OJ示例
- 【模板】二元一次不定方程 (exgcd) - 洛谷
- 线性同余方程
- 同余方程OJ示例
- 扩欧求a的逆元 Nowcoder
- 青蛙的约会 - 洛谷
- 高次同余方程
- 高次同余方程OJ示例
- 模板题计算器 - 洛谷
- 模板题BSGS - 洛谷
- 再看乘法逆元
- 求单个元素模m的逆元
- 递推求前n个数模p的逆元
- 乘法逆元OJ示例
- 递推求前n个数模p的逆元
- 求单个元素模p的逆元
不定方程
不定方程又称丢番图方程。这里只讨论二元一次不定方程 a x + b y = c ax+by=c ax+by=c,也就是算法竞赛常考的那部分。
不定方程 a x + b y = c ax+by=c ax+by=c中 x x x和 y y y都是未知数,因此这个方程又可以转换成 a x ≡ c ( m o d b ) ax\equiv c\pmod b ax≡c(modb)。
裴蜀定理
裴蜀定理又称贝祖定理:
- 一定存在整数 x , y x, y x,y,满足 a x + b y = gcd ( a , b ) ax + by = \text{gcd}(a, b) ax+by=gcd(a,b)。
可以用来判定不定方程是否存在整数解。
例如 6 x + 8 y = gcd ( 6 , 8 ) = 2 6x + 8y = \text{gcd}(6, 8) = 2 6x+8y=gcd(6,8)=2,一定有整数解。
其中 x = − 1 , y = 1 x = -1, y = 1 x=−1,y=1 就是一组解。
【裴蜀定理的推论】
- 一定存在整数 x , y x, y x,y,满足 a x + b y = gcd ( a , b ) × n ax + by = \text{gcd}(a, b) \times n ax+by=gcd(a,b)×n
因此,对于一个二元一次不定方程 a x + b y = c ax + by = c ax+by=c,当 gcd ( a , b ) ∣ c \text{gcd}(a, b) | c gcd(a,b)∣c 时,有解。
- 一定存在整数 x 1 , x 2 , x 3 , … x_1, x_2, x_3, \ldots x1,x2,x3,…,满足 a 1 x 1 + a 2 x 2 + a 3 x 3 + … a k x k = gcd ( a 1 , a 2 , a 3 , … a k ) × n a_1 x_1 + a_2 x_2 + a_3 x_3 + \ldots a_k x_k = \text{gcd}(a_1, a_2, a_3, \ldots a_k) \times n a1x1+a2x2+a3x3+…akxk=gcd(a1,a2,a3,…ak)×n。
a , b a, b a,b 的正负是不影响结果的。因为 a , b a, b a,b 如果存在解,那么 a , − b a, -b a,−b 也一定存在解,只不过在 a , b a, b a,b 解的基础上添上一个负号。
因此,在用欧几里得算法求 gcd ( a , b ) \text{gcd}(a, b) gcd(a,b) 时,为了避免出现负数,可以带入 a , b a, b a,b 的绝对值。
裴蜀定理OJ示例
这个定理只给出了不定方程的解的存在,所以这个OJ考的其实是其他知识。
【模板】裴蜀定理 - 洛谷
P4549 【模板】裴蜀定理 - 洛谷
根据推论 a 1 x 1 + a 2 x 2 + a 3 x 3 + … a k x k = gcd ( a 1 , a 2 , a 3 , … a k ) × n a_1 x_1 + a_2 x_2 + a_3 x_3 + \ldots a_k x_k = \text{gcd}(a_1, a_2, a_3, \ldots a_k) \times n a1x1+a2x2+a3x3+…akxk=gcd(a1,a2,a3,…ak)×n,方程的最小值就是求所有数的 gcd \text{gcd} gcd。
系数这里选择全改成正数。
#include<bits/stdc++.h>
using namespace std;int gcd(int a, int b) {return b ? gcd(b, a % b) : a;
}void ac() {int n; cin >> n;int ans = 1, x;--n; cin >> ans;while (n--) {cin >> x;ans = gcd(abs(ans), abs(x));}cout << ans << endl;
}int main() {int T = 1;//cin >> T;while (T--)ac();return 0;
}
扩展欧几里得算法
对 a x + b y = gcd ( a , b ) ax + by = \text{gcd}(a, b) ax+by=gcd(a,b),当 b = 0 b = 0 b=0 时, a x + b y = a ax + by = a ax+by=a,因此有一组解为: x = 1 , y = 0 x = 1, y = 0 x=1,y=0;
当 b ≠ 0 b \neq 0 b=0 时,
-
由欧几里得算法得: gcd ( a , b ) = gcd ( b , a m o d b ) \text{gcd}(a, b) = \text{gcd}(b, a \bmod b) gcd(a,b)=gcd(b,amodb)
-
由裴蜀定理得:
gcd ( a , b ) = a x + b y \text{gcd}(a, b) = ax + by gcd(a,b)=ax+bygcd ( b , a m o d b ) = b x 1 + ( a m o d b ) y 1 \text{gcd}(b, a \bmod b) = bx_1 + (a \bmod b)y_1 gcd(b,amodb)=bx1+(amodb)y1
= b x 1 + ( a − a b × b ) y 1 = bx_1 + (a - \frac{a}{b} \times b)y_1 =bx1+(a−ba×b)y1(在整数除法中存在舍弃小数部分的行为,不能约掉分母)
= a y 1 + b ( x 1 − a b y 1 ) = ay_1 + b(x_1 - \frac{a}{b}y_1) =ay1+b(x1−bay1)
因为 gcd ( a , b ) = gcd ( b , a m o d b ) \text{gcd}(a, b) = \text{gcd}(b, a \bmod b) gcd(a,b)=gcd(b,amodb),所以:
{ x = y 1 , y = x 1 − a b y 1 \begin{cases} x=y_1,\\ y=x_1-\frac{a}{b}y_1 \end{cases} {x=y1,y=x1−bay1
这组解需要根据递归和回溯过程来理解用法。
扩展欧几里得算法示例:
//可改成其他类型,用模板是为了方便拷贝
template<class intT>
intT exgcd(intT a, intT b, intT& x, intT& y) {if (b == 0) {x = 1, y = 0;return a;}intT x1, y1, d;d = exgcd(b, a % b, x1, y1);x = y1, y = x1 - a / b * y1;return d;
}
例如: gcd ( 6 , 8 , x , y ) → gcd ( 8 , 6 , x , y ) → gcd ( 6 , 2 , x , y ) → gcd ( 2 , 0 , 1 , 0 ) \text{gcd}(6,8,x,y)\rightarrow\text{gcd}(8,6,x,y)\rightarrow\text{gcd}(6,2,x,y)\rightarrow\text{gcd}(2,0,1,0) gcd(6,8,x,y)→gcd(8,6,x,y)→gcd(6,2,x,y)→gcd(2,0,1,0),
回溯: gcd ( 2 , 0 , 1 , 0 ) → gcd ( 6 , 2 , 0 , 1 ) → gcd ( 8 , 6 , 1 , − 1 ) → gcd ( 6 , 8 , − 1 , 1 ) \text{gcd}(2,0,1,0)\rightarrow\text{gcd}(6,2,0,1)\rightarrow\text{gcd}(8,6,1,-1)\rightarrow\text{gcd}(6,8,-1,1) gcd(2,0,1,0)→gcd(6,2,0,1)→gcd(8,6,1,−1)→gcd(6,8,−1,1)。
所以不定方程 6 x + 8 y = gcd ( 6 , 8 ) 6x + 8y = \text{gcd}(6, 8) 6x+8y=gcd(6,8)的1个特解是 x = − 1 , y = 1 x=-1,y=1 x=−1,y=1。
于是可以利用递归,先求出下一层的 x 1 , y 1 x_1, y_1 x1,y1,再求出当前层的 x , y x, y x,y。
上述递归过程,可以求出一组特解: x 0 , y 0 x_0, y_0 x0,y0。
然后构造出通解为:
{ x = x 0 + b gcd ( a , b ) × k y = y 0 − a gcd ( a , b ) × k \begin{cases} x = x_0 + \frac{b}{\text{gcd}(a, b)} \times k \\ y = y_0 - \frac{a}{\text{gcd}(a, b)} \times k \end{cases} {x=x0+gcd(a,b)b×ky=y0−gcd(a,b)a×k
其中 k k k 是正整数。
通解的构造: a ( x 0 + Δ x ) + b ( y 0 − Δ y ) = gcd ( a , b ) a(x_0+\Delta x)+b(y_0-\Delta y)=\text{gcd}(a,b) a(x0+Δx)+b(y0−Δy)=gcd(a,b),
将左边拆开: a x 0 + b y 0 + a Δ x − b Δ y = gcd ( a , b ) ax_0+by_0+a\Delta x-b\Delta y=\text{gcd}(a,b) ax0+by0+aΔx−bΔy=gcd(a,b),
为了和原等式对应, a Δ x − b Δ y = 0 a\Delta x-b\Delta y=0 aΔx−bΔy=0,所以 Δ x Δ y = b a \frac{\Delta x}{\Delta y}=\frac{b}{a} ΔyΔx=ab,
将 b a \frac{b}{a} ab化简得 Δ x Δ y = b gcd ( a , b ) a gcd ( a , b ) \frac{\Delta x}{\Delta y}=\frac{\frac{b}{\text{gcd}(a,b)}}{\frac{a}{\text{gcd}(a,b)}} ΔyΔx=gcd(a,b)agcd(a,b)b。所以 Δ x \Delta x Δx、 Δ y \Delta y Δy可看成 b gcd ( a , b ) \frac{b}{\text{gcd}(a,b)} gcd(a,b)b和 a gcd ( a , b ) \frac{a}{\text{gcd}(a,b)} gcd(a,b)a。
为了得到无穷的通解,引入正整数 k k k,
所以就有通解:
{ x = x 0 + b gcd ( a , b ) × k y = y 0 − a gcd ( a , b ) × k \begin{cases} x = x_0 + \frac{b}{\text{gcd}(a, b)} \times k \\ y = y_0 - \frac{a}{\text{gcd}(a, b)} \times k \end{cases} {x=x0+gcd(a,b)b×ky=y0−gcd(a,b)a×k
时间复杂度:与欧几里得算法得时间复杂度一致,为 O ( log n ) O(\log n) O(logn)。
扩欧求二元一次不定方程的解
根据扩展欧几里得算法,求解二元一次不定方程 a x + b y = c ax + by = c ax+by=c 的解的流程:
-
利用扩欧算法求出不定方程 a x + b y = gcd ( a , b ) ax + by = \gcd(a, b) ax+by=gcd(a,b) 一组特解 x 0 , y 0 x_0, y_0 x0,y0,以及系数的最大公约数 d d d;
-
由裴蜀定理判断是否有解:
a. 如果 d ∤ c d \nmid c d∤c( d d d不是 c c c的约数):无解;
b. 如果 d ∣ c d \mid c d∣c:有解。 -
如果有解:
a. 原方程的一组特解为: x 1 = x 0 × c d , y 1 = y 0 × c d x_1 = \frac{x_0 \times c}{d}, y_1 = \frac{y_0 \times c}{d} x1=dx0×c,y1=dy0×c;根据裴蜀定理的推论, c c c可能不等于 gcd ( a , b ) \text{gcd}(a,b) gcd(a,b),所以 a x 0 + b y 0 = gcd ( a , b ) ax_0+by_0=\text{gcd}(a,b) ax0+by0=gcd(a,b),记 d = gcd ( a , b ) d=\text{gcd}(a,b) d=gcd(a,b),
则 c d ( a x 0 + b y 0 ) = d × c d \frac{c}{d}(ax_0+by_0)=d\times\frac{c}{d} dc(ax0+by0)=d×dc,
所以 a ( x 0 × c d ) + b ( y 0 × c d ) = d × c d a(\frac{x_0\times c}{d})+b(\frac{y_0\times c}{d})=d\times \frac{c}{d} a(dx0×c)+b(dy0×c)=d×dc,
因为 d ∣ c d|c d∣c,所以 a ( x 0 × c d ) + b ( y 0 × c d ) = c a(\frac{x_0\times c}{d})+b(\frac{y_0\times c}{d})=c a(dx0×c)+b(dy0×c)=c,
即方程 a x + b y = c ax + by = c ax+by=c的一组特解为: x 1 = x 0 × c d , y 1 = y 0 × c d x_1 = \frac{x_0 \times c}{d}, y_1 = \frac{y_0 \times c}{d} x1=dx0×c,y1=dy0×c。
b. 根据特解可得通解: x = x 1 + b gcd ( a , b ) × k , y = y 1 − a gcd ( a , b ) × k x = x_1 + \frac{b}{\gcd(a, b)} \times k, y = y_1 - \frac{a}{\gcd(a, b)} \times k x=x1+gcd(a,b)b×k,y=y1−gcd(a,b)a×k。
扩欧定理OJ示例
【模板】二元一次不定方程 (exgcd) - 洛谷
P5656 【模板】二元一次不定方程 (exgcd) - 洛谷
借助这题将不定方程的解尽可能多的解释细节。
解决这个题需要使用扩欧算法解二元一次不定方程:
-
先用
exgcd
求出 a x + b y = gcd ( a , b ) ax+by=\text{gcd}(a,b) ax+by=gcd(a,b)的一组特解,记 d = gcd ( a , b ) d=\text{gcd}(a,b) d=gcd(a,b)。 -
d ∤ c d \nmid c d∤c:无解,否则有解。
-
根据上文推论,方程 a x + b y = c ax + by = c ax+by=c的一组特解为 x 1 = x 0 × c d , y 1 = y 0 × c d x_1 = \frac{x_0 \times c}{d}, y_1 = \frac{y_0 \times c}{d} x1=dx0×c,y1=dy0×c。利用特解可以求得通解 x = x 1 + b gcd ( a , b ) × k , y = y 1 − a gcd ( a , b ) × k x = x_1 + \frac{b}{\gcd(a, b)} \times k, y = y_1 - \frac{a}{\gcd(a, b)} \times k x=x1+gcd(a,b)b×k,y=y1−gcd(a,b)a×k。
-
观察通解可发现,当 k k k逐渐增加, x x x也逐渐增加,而 y y y反而减小,反之同理。
所以先将 x x x补正变成最小的正整数,此时 y y y即为最大值。- y y y为最大值时,当 y ≤ 0 y\leq 0 y≤0时,方程无正整数解。
- y y y为最大值时,当 y > 0 y> 0 y>0时,方程有正整数解。
关于如何补正: x = x 1 + b gcd ( a , b ) × k x = x_1 + \frac{b}{\gcd(a, b)} \times k x=x1+gcd(a,b)b×k和 5 = 1 + 2 k 5=1+2k 5=1+2k(即 5 m o d 2 = 1 5\bmod 2=1 5mod2=1)做比较发现十分相似,所以有 x m o d b gcd ( a , b ) = x 1 x\bmod \frac{b}{\gcd(a, b)}=x_1 xmodgcd(a,b)b=x1,此时:
x = { b gcd ( a , b ) , x 1 = 0 ( x 1 m o d b gcd ( a , b ) + b gcd ( a , b ) ) m o d b gcd ( a , b ) , x 1 ≠ 0 x= \begin{cases} \frac{b}{\text{gcd}(a,b)},\quad x_1=0 \\[2ex] (x_1\bmod \frac{b}{\text{gcd}(a,b)}+\frac{b}{\text{gcd}(a,b)})\bmod \frac{b}{\text{gcd}(a,b)}, \quad x_1\neq 0 \end{cases} x=⎩ ⎨ ⎧gcd(a,b)b,x1=0(x1modgcd(a,b)b+gcd(a,b)b)modgcd(a,b)b,x1=0
之后根据 y = ( c − a x ) b y=(c-ax)b y=(c−ax)b即可求得 y y y。 -
当 y ≤ 0 y\leq 0 y≤0时,说明方程没有全是正整数的解,题目要求输出 x min x_{\min} xmin和 y min y_{\min} ymin,其中 y min y_{\min} ymin需要之前求得的最大值 y y y做和 x x x同样的操作,即补正加判断是否为0,此时即可求得 y min y_{\min} ymin。
-
当 y > 0 y>0 y>0时,需要输出5个数。此时需要求最小的 x min x_{\min} xmin和做过取模处理的 y max y_{\max} ymax,以及 x max x_{\max} xmax和 y min y_{\min} ymin。
在补正时期就求得 x min = x x_{\min}=x xmin=x, y max = y y_{\max}=y ymax=y,而 y min y_{\min} ymin经过 y max y_{\max} ymax的取模操作得到, x max x_{\max} xmax通过 ( c − b y ) a \frac{(c-by)}{a} a(c−by)得到。
求有多少个正整数解,此时可以通过 x max − x min b gcd ( a , b ) + 1 \frac{x_{\max}-x_{\min}}{\frac{b}{\gcd(a, b)}}+1 gcd(a,b)bxmax−xmin+1得到(除法得到的是等差数列之间的空隙,需要加1才能得到正整数解)。
-
#include<bits/stdc++.h>
using namespace std;typedef long long LL;//可改成其他类型,用模板是为了方便拷贝
template<class intT>
intT exgcd(intT a, intT b, intT& x, intT& y) {if (b == 0) {x = 1, y = 0;return a;}intT x1, y1, d;d = exgcd(b, a % b, x1, y1);x = y1, y = x1 - a / b * y1;return d;
}void ac() {LL a, b, c;cin >> a >> b >> c;LL x1, y1, d;d = exgcd(a, b, x1, y1);if (c % d) {cout << -1 << endl;return;}//补正操作//这里需要先算c/d,因为c/d是一个整体,在整数除法顺序变了结果可能会错x1 = c / d * x1;LL g1 = b / d, g2 = a / d;//方便记录b/gcd(a,b)、a/gcd(a,b)x1 = (x1 % g1 + g1) % g1;x1 = x1 == 0 ? g1 : x1;//x_miny1 = (c - a * x1) / b;//y_max//分类讨论if (y1 <= 0) {y1 = (y1 % g2 + g2) % g2;y1 = y1 == 0 ? g2 : y1;cout << x1 << ' ' << y1 << endl;return;}else {//0表示最小值,1表示最大值LL x0 = x1, y0 = y1;//x_miny0 = (y0 % g2 + g2) % g2;y0 = y0 == 0 ? g2 : y0;//y_minx1 = (c - b * y0) / a;//x_maxcout << (x1 - x0) / g1 + 1 << ' ' << x0 << ' ' << y0<< ' ' << x1 << ' ' << y1 << endl;}
}int main() {ios::sync_with_stdio(false);cin.tie(0);int T = 1;cin >> T;while (T--)ac();return 0;
}
线性同余方程
同余方程的概念较为复杂,这里只举例算法竞赛常考的线性同余方程 a x ≡ b ( mod m ) ax \equiv b(\text{mod } m) ax≡b(mod m)。
竞赛中常考求一次同余方程(或线性同余方程) a x ≡ b ( mod m ) ax \equiv b(\text{mod } m) ax≡b(mod m) 的解。
或求方程 a x m o d m = b ax\bmod m=b axmodm=b, b ∈ [ 0 , m − 1 ] b\in [0,m-1] b∈[0,m−1]的解。
这个是个人根据实际应用时遇到的问题进行的补充。
例如:求 4 x ≡ 2 ( mod 6 ) 4x \equiv 2(\text{mod } 6) 4x≡2(mod 6),其中一个解为 x = 2 x = 2 x=2。
【扩欧算法求同余方程的解】
同余方程可以转换为二元一次不定方程:
-
由 a x ≡ b ( mod m ) ax \equiv b(\text{mod } m) ax≡b(mod m) 可得: a x = y m + b ax = ym + b ax=ym+b( a x ÷ m = y … … b ax\div m=y\dots\dots b ax÷m=y……b),移项得 a x − m y = b ax - my = b ax−my=b;
a x ≡ b ( mod m ) ax \equiv b(\text{mod } m) ax≡b(mod m)得到
a x ÷ m = y 1 … r ax\div m=y_1\dots r ax÷m=y1…r,
b ÷ m = y 2 … r b\div m=y_2\dots r b÷m=y2…r, r = b − m y 2 r=b-my_2 r=b−my2,代入 a x ÷ m = y 1 … r ax\div m=y_1\dots r ax÷m=y1…r有
a x = m y 1 + b − m y 2 = m ( y 1 − y 2 ) + b ax=my_1+b-my2=m(y_1-y_2)+b ax=my1+b−my2=m(y1−y2)+b,为了求 x x x,令 y = y 1 − y 2 y=y_1-y_2 y=y1−y2,
方程变成 a x = y m + b ax=ym+b ax=ym+b。
-
最终目的是求 x x x,只有少部分情况需要求 y y y,所以令 y = − y y = -y y=−y,就变成熟悉的二元一次不定方程: a x + m y = b ax + my = b ax+my=b;
-
由裴蜀定理得,当 gcd ( a , m ) ∣ b \text{gcd}(a, m) \mid b gcd(a,m)∣b 时,有解。
那么,就可以用扩展欧几里得算法求出 x x x 的值。
【扩欧算法求乘法逆元】
- 特殊的,如果 b b b 等于 1,原方程变为 a x ≡ 1 ( mod m ) ax \equiv 1(\text{mod } m) ax≡1(mod m),解出的 x x x 实际上就是 a a a 在模 m m m 意义下的乘法逆元。
因此,利用扩欧算法也可以求出乘法逆元。并且不需要模数 m m m 是质数,仅需 gcd ( a , m ) = 1 \text{gcd}(a, m) = 1 gcd(a,m)=1,也就是 a , m a, m a,m 互质即可。
同余方程OJ示例
扩欧求a的逆元 Nowcoder
【模板】同余方程
真模板题,同余方程 a x ≡ 1 ( m o d b ) ax\equiv1\pmod b ax≡1(modb)可转换成同余方程 a x + b y = 1 ax+by=1 ax+by=1,当 gcd ( a , b ) ≠ 1 \text{gcd}(a,b)\neq 1 gcd(a,b)=1时方程无解,否则就用扩展欧几里得算法求解。且 x ≠ 0 x\neq 0 x=0。因为 gcd ( a , b ) = 1 \text{gcd}(a,b)=1 gcd(a,b)=1, x = 0 x=0 x=0时,0除以任何数都为0,最大公约数不可能为1。
#include<bits/stdc++.h>
using namespace std;typedef long long LL;LL exgcd(LL a, LL b, LL& x, LL& y) {if (!b) {x = 1, y = 0;return a;}LL x1, y1, d;d = exgcd(b, a % b, x1, y1);x = y1; y = x1 - a / b * y1;return d;
}void ac() {LL a, b;cin >> a >> b;//ax %b = 1->ax+by=1LL x1, y1, d = exgcd(a, b, x1, y1);if (d != 1) {cout << -1 << endl;return;}//d=1,这里可省略x1 = ((x1 / d) % (b / d) + (b / d)) % (b / d);x1 = x1 == 0 ? b : x1;cout << x1 << endl;
}int main() {ios::sync_with_stdio(false);cin.tie(0);int T = 1;cin >> T;while (T--)ac();return 0;
}
青蛙的约会 - 洛谷
P1516 青蛙的约会 - 洛谷
1631:【例 1】青蛙的约会
根据题意可得 ( x + m t ) ≡ ( y + n t ) ( m o d L ) (x+mt)\equiv(y+nt)\pmod L (x+mt)≡(y+nt)(modL),例如样例:
( 1 + 3 t ) ≡ ( 2 + 4 t ) ( m o d 5 ) (1+3t)\equiv(2+4t)\pmod 5 (1+3t)≡(2+4t)(mod5),当 t = 4 t=4 t=4时成立。
这里目标是求 t t t,所以将方程 ( x + m t ) ≡ ( y + n t ) ( m o d L ) (x+mt)\equiv(y+nt)\pmod L (x+mt)≡(y+nt)(modL)转化成等式:
( x + m t ) m o d L = ( y + n t ) m o d L (x+mt)\bmod L=(y+nt)\bmod L (x+mt)modL=(y+nt)modL,因为 x , y ≤ L x,y\leq L x,y≤L,所以
x + ( m t ) m o d L = y + ( n t ) m o d L x+(mt)\bmod L=y+(nt)\bmod L x+(mt)modL=y+(nt)modL,移项得
( ( m − n ) t ) m o d L = y − x ((m-n)t)\bmod L=y-x ((m−n)t)modL=y−x,
设 a = m − n a=m-n a=m−n, b = y − x b=y-x b=y−x,则原来的方程变成
a t m o d L = b at\bmod L=b atmodL=b( a t ≡ b ( m o d L ) at\equiv b\pmod L at≡b(modL)),
所以问题变成了求 a t + L y = b at+Ly=b at+Ly=b这个方程的最小整数解, t t t的最小整数解就是答案。
但因为过程中出现很多减法,需要先进行补正操作。
#include<bits/stdc++.h>
using namespace std;typedef long long LL;LL exgcd(LL a, LL b, LL& x, LL& y) {if (!b) {x = 1, y = 0;return a;}LL x1, y1, d = exgcd(b, a % b, x1, y1);x = y1; y = x1 - a / b * y1;return d;
}void ac() {LL x, y, m, n, l;cin >> x >> y >> m >> n >> l;//求ax+by=c的解LL a = ((m - n) % l + l) % l, b = l, c = ((y - x) % l + l) % l;LL d = exgcd(a, b, x, y);if (c % d) {cout << "Impossible" << endl;return;}x = x * c / d;x = (x % (b / d) + (b / d)) % (b / d);cout << x << endl;
}int main() {ios::sync_with_stdio(false);cin.tie(0);int T = 1;//cin >> T;while (T--)ac();return 0;
}
高次同余方程
问题:给定整数 a , b , p a, b, p a,b,p,其中 a , p a, p a,p 互质,求一个非负整数 x x x,使得 a x ≡ b ( mod p ) a^x \equiv b (\text{mod } p) ax≡b(mod p)。
该问题利用 BSGS(baby step giant step)算法求解。
baby step gaint step,婴儿一小步,巨人一大步。
由欧拉定理的推论知 a x ≡ a x m o d φ ( x ) ( m o d p ) a^{x}\equiv a^{x\bmod \varphi(x)}\pmod p ax≡axmodφ(x)(modp),因为 φ ( x ) = x × p 1 − 1 p 1 × p 2 − 1 p 2 × ⋯ × p k − 1 p k \varphi(x) = x \times \frac{p_1 - 1}{p_1} \times \frac{p_2 - 1}{p_2} \times \cdots \times \frac{p_k - 1}{p_k} φ(x)=x×p1p1−1×p2p2−1×⋯×pkpk−1,后面的 p i − i p i \frac{p_i-i}{p_i} pipi−i都小于1,所以 φ ( x ) < x \varphi(x)<x φ(x)<x,理论上枚举[1,p]
就能找到高次同余方程的解。
BSGS算法:
设 x = i m − j x = im - j x=im−j,其中 m = ⌈ p ⌉ , 0 ≤ j ≤ m − 1 m = \lceil\sqrt{p}\rceil, 0 \leq j \leq m-1 m=⌈p⌉,0≤j≤m−1,则方程变为 a i m − j ≡ b ( mod p ) a^{im-j} \equiv b (\text{mod } p) aim−j≡b(mod p)。根据同乘性有 ( a m ) i ≡ b × a j ( mod p ) (a^{m})^i \equiv b\times a^j (\text{mod } p) (am)i≡b×aj(mod p)。
-
对于所有的 j ∈ [ 0 , m − 1 ] j \in [0, m-1] j∈[0,m−1],把 b × a j m o d p b \times a^j \bmod p b×ajmodp 插入一个 Hash 表。
-
枚举 i i i 的所有可能取值,即 i ∈ [ 0 , m ] i \in [0, m] i∈[0,m],计算出 ( a m ) i mod p (a^m)^i \text{ mod } p (am)i mod p,在 Hash 表中查找是否存在对应的 j j j,存在的话更新结果 x = i m − j x = im - j x=im−j。其中第1次出现的结果即为最小正整数解。
时间复杂度为 O ( p ) O(\sqrt{p}) O(p)。
需要处理很多边界条件,具体在代码中实现。至于为什么会有这些边界条件,等本人到达那个境界再补充。
下面给出利用 BSGS 求解同余方程 a x ≡ b mod p a^x \equiv b \text{ mod } p ax≡b mod p 的最小非负整数解,无解时输出 − 1 -1 −1。
【代码实现】
typedef long long LL;
//求a^x % p == b的解
LL BSGS(LL a, LL b, LL p) {a %= p; b %= p;if (a == 0)//a%p = b%p=0, 方程有无限多个解return b == 0 ? 1 : -1;if (b == 1)//a^0 %1 =1成立return 0;//BSGS要求a、p互质,否则a模p的乘法逆元可能不存在//扩展为Pohlig-Hellman 算法 或先处理公共因子LL x, y, d = exgcd(a, p, x, y);if (d > 1) {if (b % d)return -1;//方程转化为 (a/d)^x ≡ (b/d) * (a/d)^{-1} (mod p/d)p /= d;d = exgcd(a / d, p, x, y);if (d != 1)return -1;x = (x % p + p) % p;x = x == 0 ? p : x;//求a/d的逆元b = qmul(b / d, x, p);return BSGS(a, b, p) + 1;}map<LL, LL>mp;//键值对LL m = sqrt(p) + 1;//预处理b^jfor (LL j = 0, step = b; j < m; j++) {if (!mp.count(step))mp[step] = j;step = qmul(step, a, p);}//预处理a^mLL am = 1;for (LL i = 1; i <= m; i++)am = qmul(am, a, p);for (LL i = 1, step = 1; i <= m; i++) {step = qmul(step, am, p);if (mp.count(step))return i * m - mp[step];}return -1;
}
高次同余方程OJ示例
模板题计算器 - 洛谷
[P2485 SDOI2011] 计算器 - 洛谷
模板题,但高次同余方程需要规避很多边界条件。
#include<bits/stdc++.h>
using namespace std;typedef long long LL;LL exgcd(LL a, LL b, LL& x, LL& y) {if (!b) {x = 1; y = 0;return a;}LL x1, y1, d = exgcd(b, a % b, x1, y1);x = y1; y = x1 - a / b * y1;return d;
}LL qmul(LL a, LL b, LL m) {LL ans = 0;while (b) {if (b % 2)ans = (ans + a) % m;a = (a * 2) % m;b /= 2;}return ans;
}//求a^x % p == b的解
LL BSGS(LL a, LL b, LL p) {a %= p; b %= p;if (a == 0)//a%p = b%p=0, 方程有无限多个解return b == 0 ? 1 : -1;if (b == 1)//a^0 %1 =1成立return 0;//BSGS要求a、p互质,否则a模p的乘法逆元可能不存在//扩展为Pohlig-Hellman 算法 或先处理公共因子LL x, y, d = exgcd(a, p, x, y);if (d > 1) {if (b % d)return -1;//方程转化为 (a/d)^x ≡ (b/d) * (a/d)^{-1} (mod p/d)p /= d;d = exgcd(a / d, p, x, y);if (d != 1)return -1;x = (x % p + p) % p;x = x == 0 ? p : x;//求a/d的逆元b = qmul(b / d, x, p);return BSGS(a, b, p) + 1;}map<LL, LL>mp;//键值对LL m = sqrt(p) + 1;//预处理b^jfor (LL j = 0, step = b; j < m; j++) {if (!mp.count(step))mp[step] = j;step = qmul(step, a, p);}//预处理a^mLL am = 1;for (LL i = 1; i <= m; i++)am = qmul(am, a, p);for (LL i = 1, step = 1; i <= m; i++) {step = qmul(step, am, p);if (mp.count(step))return i * m - mp[step];}return -1;
}void ac(LL k) {LL y, z, p;cin >> y >> z >> p;if (k == 1) {//快速幂LL ans = 1;while (z) {if (z % 2)ans = qmul(ans, y, p);y = qmul(y, y, p);z /= 2;}cout << ans << endl;}else if (k == 2) {//求同余方程y*x+p*t=zLL x, t, d = exgcd(y, p, x, t);if (z % d) {cout << "Orz, I cannot find x!\n";return;}x = qmul(x, z / d, p / d);x = (x % p + p) % p;cout << x << endl;}else {LL ans = BSGS(y, z, p);if (ans == -1)cout << "Orz, I cannot find x!\n";elsecout << ans << endl;}
}int main() {ios::sync_with_stdio(false);cin.tie(0);LL T, K;cin >> T >> K;while (T--)ac(K);return 0;
}
模板题BSGS - 洛谷
[P3846 TJOI2007] 可爱的质数/【模板】BSGS - 洛谷
还是模板题。
#include<bits/stdc++.h>
using namespace std;typedef long long LL;LL exgcd(LL a, LL b, LL& x, LL& y) {if (!b) {x = 1; y = 0;return a;}LL x1, y1, d = exgcd(b, a % b, x1, y1);x = y1; y = x1 - a / b * y1;return d;
}LL qmul(LL a, LL b, LL m) {LL ans = 0;while (b) {if (b % 2)ans = (ans + a) % m;a = (a * 2) % m;b /= 2;}return ans;
}//求a^x % p == b的解
LL BSGS(LL a, LL b, LL p) {a %= p; b %= p;if (a == 0)//a%p = b%p=0, 方程有无限多个解return b == 0 ? 1 : -1;if (b == 1)//a^0 %1 =1成立return 0;//BSGS要求a、p互质,否则a模p的乘法逆元可能不存在//扩展为Pohlig-Hellman 算法 或先处理公共因子LL x, y, d = exgcd(a, p, x, y);if (d > 1) {if (b % d)return -1;//方程转化为 (a/d)^x ≡ (b/d) * (a/d)^{-1} (mod p/d)p /= d;d = exgcd(a / d, p, x, y);if (d != 1)return -1;x = (x % p + p) % p;x = x == 0 ? p : x;//求a/d的逆元b = qmul(b / d, x, p);return BSGS(a, b, p) + 1;}map<LL, LL>mp;//键值对LL m = sqrt(p) + 1;//预处理b^jfor (LL j = 0, step = b; j < m; j++) {if (!mp.count(step))mp[step] = j;step = qmul(step, a, p);}//预处理a^mLL am = 1;for (LL i = 1; i <= m; i++)am = qmul(am, a, p);for (LL i = 1, step = 1; i <= m; i++) {step = qmul(step, am, p);if (mp.count(step))return i * m - mp[step];}return -1;
}int main() {ios::sync_with_stdio(false);cin.tie(0);LL p, b, n;cin >> p >> b >> n;p = BSGS(b, n, p);if (p == -1)cout << "no solution";elsecout << p << endl;return 0;
}
再看乘法逆元
【乘法逆元】
乘法逆元,一般用于求 a b ( m o d p ) \frac{a}{b}\pmod p ba(modp) 的值。
先算出 b b b 在模 p p p 意义下的乘法逆元 b − 1 b^{-1} b−1,然后计算 a × b − 1 ( mod p ) a \times b^{-1} (\text{mod } p) a×b−1(mod p) 的值,就可以将除法转化为乘法。即 a b ≡ a × b − 1 ( m o d p ) \frac{a}{b}\equiv a\times b^{-1}\pmod p ba≡a×b−1(modp)。
求单个元素模m的逆元
【方法一:费马小定理 + 快速幂】
-
问题:求 a a a 在模 m m m 意义下的逆元。
-
前置知识回顾:
- 费马小定理:如果 p p p 为质数,且 a , p a, p a,p 互质,则 a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1\pmod p ap−1≡1(modp)。
且 a × a p − 2 ≡ 1 ( m o d p ) a\times a^{p-2} \equiv 1\pmod p a×ap−2≡1(modp),则 a p − 2 a^{p-2} ap−2就是 a a a的模 p p p乘法逆元。 - 快速幂:利用二进制和模运算特性求 a b m o d m a^b\bmod m abmodm的算法。
- 费马小定理:如果 p p p 为质数,且 a , p a, p a,p 互质,则 a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1\pmod p ap−1≡1(modp)。
-
前提条件: a , m a, m a,m 互质,且 m m m 是质数;
-
方法:求出 a m − 2 m o d m a^{m-2}\bmod m am−2modm,就是 a a a 在模 m m m 意义下的逆元。
-
时间复杂度: O ( log m ) O(\log m) O(logm)。
【方法二:扩欧算法求逆元】
-
问题:求 a a a 在模 m m m 意义下的逆元。
-
前置知识回顾:
-
扩展欧几里得算法的结论:对不定方程 a x + b y = gcd ( a , b ) ax+by=\text{gcd}(a,b) ax+by=gcd(a,b),可通过一下结论和递归求得不定方程的特解:
{ x = y 1 , y = x 1 − a b y 1 \begin{cases} x=y_1,\\ y=x_1-\frac{a}{b}y_1 \end{cases} {x=y1,y=x1−bay1
其中递归的边界条件是 b = 0 b=0 b=0,此时 x 1 = 1 , y 1 = 0 x_1=1,y_1=0 x1=1,y1=0。
-
-
前提条件: a , m a, m a,m 互质;
-
方法:解同余方程 a x ≡ 1 ( m o d m ) ax \equiv 1 \pmod m ax≡1(modm)(或 a x m o d m = 1 ax\bmod m=1 axmodm=1)。将方程转换成 a x + m y = 1 ax+my=1 ax+my=1,求方程的解中 x x x可能得值。
-
时间复杂度: O ( log m ) O(\log m) O(logm)。
递推求前n个数模p的逆元
-
问题:求 1 ∼ n 1 \sim n 1∼n 中所有的数在模 p p p 意义下的逆元。
-
前提条件: 1 ∼ n 1 \sim n 1∼n 中所有的数都与 p p p 互质。最常见的情形是 p > n p>n p>n且 gcd ( n , p ) = 1 \text{gcd}(n,p)=1 gcd(n,p)=1。
-
方法:线性递推:
对 1 ∼ n m o d p 1\sim n \bmod p 1∼nmodp:
-
当 i = 1 i = 1 i=1:
inv[1] = 1
。 -
当 i > 1 i > 1 i>1:
inv[i] = p - (p/i) * inv[p % i] % p
。设 i ∈ { 1 , 2 , … , n } i\in \{1,2,\dots,n\} i∈{1,2,…,n},则 p ÷ i = k … r p\div i=k\dots r p÷i=k…r,
于是 p = i × k + r p=i\times k+r p=i×k+r,
所以有 ( i × k + r ) ≡ 0 ( m o d p ) (i\times k+r)\equiv 0\pmod p (i×k+r)≡0(modp),或 ( i × k + r ) m o d p = 0 (i\times k+r)\bmod p=0 (i×k+r)modp=0。
两边同时乘 i − 1 m o d p i^{-1}\bmod p i−1modp和 r − 1 m o d p r^{-1}\bmod p r−1modp有
( k r − 1 + i − 1 ) ≡ 0 ( m o d p ) (kr^{-1}+i^{-1})\equiv 0\pmod p (kr−1+i−1)≡0(modp)( ( k r − 1 + i − 1 ) m o d p = 0 (kr^{-1}+i^{-1})\bmod p=0 (kr−1+i−1)modp=0),
所以 i − 1 m o d p = − k r − 1 m o d p i^{-1}\bmod p=-kr^{-1}\bmod p i−1modp=−kr−1modp,即 i − 1 ≡ ( − k r − 1 ) ( m o d p ) i^{-1}\equiv (-kr^{-1})\pmod p i−1≡(−kr−1)(modp)
k k k和 r − 1 m o d p r^{-1}\bmod p r−1modp用 i i i和 p p p代替有
i − 1 ≡ ( − p i × ( p m o d i ) − 1 ) ( m o d p ) i^{-1}\equiv (-\frac{p}{i}\times (p\bmod i)^{-1})\pmod p i−1≡(−ip×(pmodi)−1)(modp),或
i − 1 m o d p = ( − p i × ( p m o d i ) − 1 ) m o d p i^{-1}\bmod p=(-\frac{p}{i}\times (p\bmod i)^{-1})\bmod p i−1modp=(−ip×(pmodi)−1)modp,为防止出现负数,进行补正操作:
i − 1 m o d p = p − ( ( p i × ( p m o d i ) − 1 ) m o d p ) i^{-1}\bmod p=p-((\frac{p}{i}\times (p\bmod i)^{-1})\bmod p) i−1modp=p−((ip×(pmodi)−1)modp)
所以根据这个递推式即可求得 1 ∼ n 1 \sim n 1∼n 中所有的数在模 p p p 意义下的逆元。
-
-
时间复杂度: O ( n ) O(n) O(n)。
乘法逆元OJ示例
递推求前n个数模p的逆元
P3811 【模板】模意义下的乘法逆元 - 洛谷
套用递推式即可。但输入、输出的数据量比较大,需要对cin
进行优化。
#include<bits/stdc++.h>
using namespace std;typedef long long LL;void ac() {LL n, p;cin >> n >> p;vector<LL>inv(n + 1, 0);inv[1] = 1;for (LL i = 2; i <= n; i++) {//2个都可以inv[i] = p - ((p / i * inv[p % i]) % p);//inv[i] = p - p / i * inv[p % i] % p;//优先级:(* /) % (+ -)}for (LL i = 1; i <= n; i++)cout << inv[i] << '\n';
}int main() {ios::sync_with_stdio(false);cin.tie(0);int T = 1;//cin >> T;while (T--)ac();return 0;
}
求单个元素模p的逆元
【模板】逆元
题目没保证 p p p是质数,因此不能用费马小定理。所以用扩展欧几里得定理求解。
这题和【模板】同余方程可以共用同一个AC的代码,准确来说是同一个题。
#include<bits/stdc++.h>
using namespace std;typedef long long LL;//扩展欧几里得算法求不定方程的特解
LL exgcd(LL a,LL b,LL&x, LL&y){if(!b){x=1,y=0;return a;}LL x1,y1,d=exgcd(b,a%b,x1,y1);x=y1,y=x1-a/b*y1;return d;
}void ac() {LL a,p;cin>>a>>p;//求逆元转化为求ax+py=1的解LL x,y,d=exgcd(a,p,x,y);if(d!=1){cout<<-1<<'\n';return;}x=(x+p)%p;cout<<x<<'\n';
}int main() {ios::sync_with_stdio(false);cin.tie(0);int T = 1;cin >> T;while (T--)ac();return 0;
}