数论3:线性同余方程组
欧几里得算法
又称辗转相处法,用以求两个数的最大公约数,引入符号\((a,b)\)表示a、b的最大公约数,\(a|b\)表示a能整除b,\(a\not |\ \ b\)表示a不能整除b。
现要证明\((a,b) = (b,a \mod b)\)
即证\(a = bq + r\)中\((a,b)=(b,r)\)
令\((a,b) = d_1\),\((b,r) = d_2\)
有\(d_1(a^, - b^,q) = r\),即\(d_1 | r\)
其已知\(d_1 | b\),所以\(d_1 | (b,r)\),即\(d_1 | d_2\)
同理有\(d_2 | d_1\),所以得\(d_1 = d_2\)
因为\((n, 0) = n\),所以这也是欧几里得算法的出口。
时间复杂度\(O(\lg \max(a,b))\),因为每次迭代数字的大小至少减少一半。
int gcdr(int a, int b)
{
if(b == 0) return a;
return gcd(b, a%b);
}
int gcd(int a, int b)
{
while(b) {
int r = a % b;
a = b, b = r;
}
return a;
}
扩展欧几里得算法
用以求不定方程\(ax + by = (a,b)\)的一组特解,为方便令\((a,b) = d\),引入符号\(\lfloor \frac a b \rfloor\)表示下取整,讨论其解法。
当\(b=0\)时,特解\(x=1, y=0\)。
当\(b\neq 0\),令\(r=a \mod b\)考虑方程
因为\((a,b)=(b,r)\),所以
而\(r = a \mod b = a - \lfloor \frac a b \rfloor b\),所以
整理得
得递归解
现一组特解为\(x_0, y_0\),则通解为
时间复杂度同样为\(O(\lg\max(a,b))\),因为递归的执行次数的决定因素和欧几里得算法一样。
int exgcd(int a, int b, int &x, int &y)
{
if(b == 0) { x = 1, y = 0; return a; }
int d = exgcd(b, a%b, y, x);
y -= (a / b) * x;
return d;
}
扩展欧几里得算法求\(ax + by = c\)
可以通过\(ax_0 + by_0 = d\)的特解放大倍数求得。
若\(d \not |\ \ c\),则无解;
若\(d \ \ \, |\ \ c\),则无解有以下讨论:
两边同时乘\(\frac c d\),可有
特解为
通解为
// 有解返回true,否则false
bool solve1(int a, int b, int c, int &x, int &y)
{
int d = exgcd(a, b, x, y);
if(c % d != 0) return 0;
int t = c / d;
x *= t, y *= t;
return 1;
}
扩展欧几里得算法求\(ax \equiv b \pmod m\)
\(a \equiv b \pmod m\),a、b同余代表除m时余数相同,即\(a - \lfloor \frac a m \rfloor m = b - \lfloor \frac b m \rfloor m\),整理得
所以上述同余方程可写为:
这就可以用扩展欧几里得算法求\(ax + by = c\)方法求解了。
// 有解返回true,否则false
bool solve2(int a, int b, int m, int &x)
{
int y;
return solve1(a, -m, b, x, y);
}
线性同余方程组\(a_ix \equiv b_i \pmod{m_i}\)
我们无法做到一次求解多个方程,那么考虑如何将其化为单一方程求解。
我们的目标是\(x = b \pmod m\),即b和m。
为方便解决,引入方程\(x \equiv 0 \pmod 1\),现只需要考虑以下两个方程的合并问题
由①得到
代入②得
仅当\(d=(a_1m_0, m_1) | (b_1 - a_1b_0)\)时,整个方程组有解。
通过扩展欧几里得算法求\(ax \equiv b \pmod m\)的方法求得解\(u = u_0 \pmod {\frac {m_1}{d}}\),带回③得,\(x = b_0 + m_0u_0 + \frac {m_0m_1}{d}t\),即
至此合并完成。
int n, A[maxn], B[maxn], M[maxn];
// 有解返回true,否则false
bool solve_equ_set(int &b0, int &m0)
{
int u0;
b0 = 0, m0 = 1;
for(int i = 0; i < n; ++i) {
int na = A[i] * m0;
int nb = B[i] - A[i]*b0;
int d = gcd(na, M[i]);
if(!solve2(na, nb, M[i], u0)) return 0;
b0 += m0 * u0;
m0 *= M[i] / d;
b0 %= m0;
}
return 1;
}