【数学】同余方程解法合集

包括(一元、多元)线性同余方程(组)、高次同余方程的解法模板。

线性同余方程

一元线性同余方程

形式:axb(modm)ax\equiv b(\mod m) ,求解xx ,引入逆元后可转化为xba1(modm)x\equiv ba^{-1}(\mod m)

等价式ax+my=b,yNax+my=b,y\in N

有解:gcd(a,m)=d\gcd(a,m)=ddbd\mid b 则有dd 个模mm 不同余的解,否则无解

解法:转化为ax+my=dax+my=d ,则乘b/db/d 即可得到原式的解。

1
2
3
4
5
6
7
8
9
10
11
// 求解ax+my=b的x,即ax==b(mod m),答案存在ans[0...d-1]中,返回值d为答案个数
int mod_equation(int a, int b, int m)
{
exgcd(a, m, d, x, y);
if (b % d) // 无解
return -1;
x = x * (b / d) % m;
for (int i = 0; i < d; i++)
ans[i] = (x + i * m / d) % m;
return d;
}

一元线性同余方程组

形如:

{xa1(modm1)xa2(modm2)xan(modmn)\begin{cases} x\equiv a_1&(\mod m_1)\\ x\equiv a_2&(\mod m_2)\\ &\vdots \\ x\equiv a_n&(\mod m_n)\\ \end{cases}

方法一:两两相消

先看两个方程:

{xa1(modm1)xa2(modm2)\begin{cases} x\equiv a_1(\mod m_1)\\ x\equiv a_2(\mod m_2)\\ \end{cases}

转化为

\begin{numcases}{} x+m_1k_1=a_1\tag{1}\\ x+m_2k_2=a_2\\ \end{numcases}

则得到a1m1k1=a2m2k2a_1-m_1k_1=a_2-m_2k_2 ,即m1k1m2k2=a1a2m_1k_1-m_2k_2=a_1-a_2 ,则有解的等价条件为gcd(m1,m2)(a1a2)\gcd(m_1,m_2)\mid(a_1-a_2) 。用上一节的一元线性同余方程解法解出k1k_1 的一个特解kk' ,于是通解k1=k+cm2gcd(m1,m2),cNk_1=k'+\frac{cm_2}{\gcd(m_1,m_2)},c\in N 。代入(1)(1) 式可得,x=a1m1k+cm1m2gcd(m1,m2)x=a_1-m_1k'+\frac{cm_1m_2}{\gcd(m_1,m_2)} 。于是转换为了新的同余方程:xa1m1k(modlcm(m1,m2))x\equiv a_1-m_1k'(\mod \text{lcm}(m_1,m_2)) 。依次合并即可解得最终答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 求解 x==a[] mod m[],数组从0开始编号,共n个式子
int mod_equations(int a[], int m[], int n)
{
int a1 = a[0], a2, m1 = m[0], m2, k1, k, d;
for (int i = 1; i < n; i++)
{
a2 = a[i], m2 = m[i];
int k2, tmp;
exgcd(m1, m2, d, k1, k2);
if ((a2 - a1) % d)
return -1;
tmp = m2 / d;
k1 = (k1 * (a2 - a1) / d % tmp + tmp) % tmp; // 特解k',保证最小正数
a1 += m1 * k1;
m1 *= m2 / d;
a1 = (a1 + m1) % m1;
}
return a1; // 通解是a1+cm1,c是整数
}

方法二:中国剩余定理

条件:原方程组m1,m2mnm_1,m_2\cdots m_n 互素

定理:有模M=m1m2mnM=m_1\cdot m_2\cdots m_n 的唯一解

推导:令Mi=M/miM_i=M/m_i ,由互素知gcd(Mi,mi)=1\gcd(M_i,m_i)=1 ,又MiMi11(modmi)M_i\cdot M_i^{-1}\equiv 1(\mod m_i) ,则通解为cM+i=1naiMiMi1cM+\sum_{i=1}^{n}a_iM_iM_i^{-1}

1
2
3
4
5
6
7
8
9
10
11
12
13
// 中国剩余定理解一元线性同余方程组,数组从0标号
ll CRT(int a[], int m[], int n)
{
ll M = 1ll, ans = 0, tmp;
for (int i = 0; i < n; i++)
M *= m[i];
for (int i = 0; i < n; i++)
{
tmp = M / m[i];
ans = (ans + a[i] * tmp % M * inv(tmp) % M) % M;
}
return ans;
}

多元线性同余方程

形如:a1x1+a2x2++anxnb(modm)a_1x_1+a_2x_2+\cdots+a_nx_n\equiv b(\mod m)

a1x+a2y=gcd(a1,a2)a_1x+a_2y=\gcd(a_1,a_2) 有解可知左式能表达cgcd(a1,a2),cNc\gcd(a_1,a_2),c\in N ;引入a3a_3 ,则gcd(a1,a2)x+a3ygcd(a_1,a_2)x+a_3y 可以表示cgcd(a1,a2,a3),cNc\gcd(a_1,a_2,a_3),c\in N 。以此类推,a1x1+a2x2++anxna_1x_1+a_2x_2+\cdots+a_nx_n

可以表达cgcd(a1,a2,,an),cNc\gcd(a_1,a_2,\cdots,a_n),c\in N ,因此有解的条件为gcd(a1,a2,,an,m)b\gcd(a_1,a_2,\cdots, a_n,m)\mid b

一个很好理解的推导:我们将mm 添加到aa 数列中,仍设数量为nn 。设d=gcd(a1,a2,,an),d1=gcd(a1,a2,,an1)d=\gcd(a_1,a_2,\cdots, a_n),d_1=\gcd(a_1,a_2,\cdots, a_{n-1}) ,于是有gcd(d1,an)=d\gcd(d_1,a_n)=d ,则原式转化为anxn+d1k=ba_nx_n+d_1k=b ,可以通过前面一元线性同余方程的解法解出xn,kx_n,k ,则问题转化为a1x1+a2x2++an1xn1=d1ka_1x_1+a_2x_2+\cdots+a_{n-1}x_{n-1}=d_1*k,仍采取相同的解法。

O(n2)O(n^2) 求每次的gcd\gcd 显然不现实。设di=gcd(a1,a2,,ai)d_i=\gcd(a_1,a_2,\cdots,a_{i})x[1...n]x[1...n] 表示解(从11 标号,a[n+1]a[n+1]mm )则可以求解diXi+ai+1yi=di+1d_iX_i+a_{i+1}y_i=d_{i+1}Xi,yiX_i,y_i 。观察最后一个式子:dnXn+an+1yn=dn+1d_{n}X_{n}+a_{n+1}y_{n}=d_{n+1} ,要求解答案,等式两边乘上c=bdn+1c=\frac{b}{d_{n+1}} ,所以xn+1=yncx_{n+1}=y_{n}cban+1xn+1=dnXncb-a_{n+1}x_{n+1}=d_{n}X_{n}c ;则前一个式子dn1Xn1+anyn1=dnd_{n-1}X_{n-1}+a_{n}y_{n-1}=d_{n} ,要求解答案,等式两边乘上c=cXnc'=cX_{n} ,依次求解即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 求解a1x1+a2x2+...+anxn==b mod m,a从1开始标号
int X[N], y[N];
bool mod_Xn_equation(int a[], int b, int m, int n, int x[])
{
a[n + 1] = m;
int d = a[1];
for (int i = 2; i <= n + 1; i++)
d = exgcd(d, a[i], X[i - 1], y[i - 1]); // 这个exgcd返回求解的ax+by=d的
if (b % d)
return false;
int c = b / d;
x[n + 1] = y[n] * c;
y[0] = 1;
for (int i = n; i >= 1; i--)
{
c = (c * X[i] % m + m) % m;
x[i] = (y[i - 1] * c % m + m) % m;
}
return true;
}

高次同余方程

只考虑:axb(modm)a^x\equiv b(\mod m)

为保持统一,放一下手写哈希

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// (x,y)的HASH
#define MOD 76543
int hs[MOD], head[MOD], nxt[MOD], id[MOD], top;
void insert(int x, int y)
{
int k = x % MOD;
hs[top] = x, id[top] = y, nxt[top] = head[k], head[k] = top++;
}
int find(int x)
{
int k = x % MOD;
for (int i = head[k]; i != -1; i = nxt[i])
if (hs[i] == x)
return id[i];
return -1;
}

BSGS算法

条件:gcd(a,m)=1\gcd(a,m)=1

x=kst,s=m,0t<s,0<ks+1x=ks-t,s=\lceil \sqrt m\rceil,0\leq t<s,0<k\leq s+1ss 的取值有严格证明这样复杂度最低),则原式变为aksbat(modm)a^{ks}\equiv ba^{t}(\mod m) ,枚举可能的tt ,存下batba^t 的哈希值,再枚举左边的kkaksa^{ks} 查表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 普通BSGS: a^x==b mod m, gcd(a,m)=1
int BSGS(int a, int b, int m)
{
if(b == 1 || m == 1) // 注意这里的特判
return 0;
int s = sqrt(m) + 1;
memset(head,-1,sizeof(head));
top=0;
for (int i = 0, tmp = b; i < s; i++, tmp = tmp * a % m)
insert(tmp, i);
int as = qpow(a, s, m); // 快速幂算a^s
for (int i = 1, tmp = as; i <= s + 1; i++, tmp = tmp * as % m)
{
int qry = find(tmp);
if (qry == -1)
continue;
return i * s - qry;
}
return -1;
}

扩展BSGS算法

条件:gcd(a,m)=d1\gcd(a,m)=d\neq 1

等价变换为ax+my=ba^x+my=b ,有解的条件是dbd\mid b 。两边同除dd ,则变为adax1+ymd=bd\frac{a}{d}a^{x-1}+y\frac{m}{d}=\frac{b}{d} ,不断检查gcd(md,a)\gcd(\frac{m}{d},a) 并除掉,直到为11 ,记每次除的数乘积为DD ,则原式变为akDaxk+ymD=bD\frac{a^k}{D}a^{x-k}+y\frac{m}{D}=\frac{b}{D} ,即axkbDinv(akD)(modmD)a^{x-k}\equiv \frac{b}{D}inv(\frac{a^k}{D})(\mod \frac{m}{D}) ,用BSGS求解即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int exBSGS(int a, int b, int m)
{
if (b == 1 || m == 1)
return 0;
int k = 0, atmp = 1;
while (1)
{
int tmp = __gcd(m, a);
if (tmp == 1)
break;
if(b % tmp)
return -1;
b /= tmp, m /= tmp, k++;
atmp = atmp * (a / tmp) % m;
if (b == atmp)
return k;
} // 此时b=b/D, m=m/D, atmp=a^k / D
int ans = BSGS(a, b * inv(atmp, m) % m, m);
return ans == -1 ? ans + k;
}