中国剩余定理及其变形
中国剩余定理
解方程组:
\[x \equiv a_1 \pmod {m_1} \\
x \equiv a_2 \pmod {m_2} \\
... \\
x \equiv a_k \pmod {m_k} \\
其中m_1, m_2...m_k两两互质
\]
记
\[M = \prod^{k}_{i = 1} m_i \\
M_i = \frac{M}{m _ i}
\]
公式解:
\[x = \sum^{k}_{i = 1} a_iM_iM_i^{-1}
\]
求\(M_i^{-1}\):
模\(n\)意义下,非零整数\(x\)的逆元存在的充要条件是:\(x\)与\(n\)互质
因为\(M_i\)与\(m_i\)互质,故\(M_i^{-1}(M_i的逆元)\)可求
求逆元:应用拓展欧几里得算法,解方程
\[M_i \cdot M_i^{-1} \equiv 1 \pmod {m_i}
\]
变形(AcWing 204. 表达整数的奇怪方式)
解方程组:求一个最小的非负整数\(x\)满足
\[x \equiv a_1 \pmod {m_1} \\
x \equiv a_2 \pmod {m_2} \\
... \\
x \equiv a_k \pmod {m_k} \\
其中m_1, m_2...m_k不一定两两互质
\]
策略:两两合并(我也不知道是怎么想到的==)
先考虑前两个方程,将其转化为等式形式
\[x = k_1 \cdot a_1 + m_1(1) \\
x = k_2 \cdot a_2 + m_2(2) \\
做差得\\
ka_1 - ka_2 = m_2 - m _1,形如ax+by = m,可使用拓展欧几里得算法求解k_1,k_2 \\
有解条件:(a_1, a_2) |m_2 - m _ 1 \\
设通解为 k = k_1 + \lambda \frac{a_2}{d}
\]
将\(通解k\)代入(1)得:
\[\begin{align*}
x & = a_1k_1 + m_1 + k_1 \frac{a_1 a_2}{d}\\
& =a_1k_1 + m_1 + k_1[a_1,a_2] \\
& = x_0 + k_1a'
\end{align*}
\]
但是在实际程序运行过程中,由于数据范围比较极限,所以过程中要缩小\(k_1\),于是我们把\(k_1\)中的因子\(t = \frac{a_2}{d}\)都除掉
\[令k’ = (k_1 \bmod t + t) \bmod t
\]
这个\(k'\)是同时满足(1),(2)的,在代码中我们把\(k_1\)替换为\(k'\)。
同样的\(x_0\)也是同时满足(1),(2)的,所以满足方程
\[x = x_0 + k_1a'
\]
的解就同时是(1),(2)的解
以上,我们就得到了一条新的方程,它的解同时满足(1)和(2),于是我们只要合并n - 1次就能得到一条解满足所有方程的方程
\[x' = x_0' + k'a'' \\
其中x_0',a''已知
\]
题目要使\(x\)最小且非负,我们只需把\(x'\)中的因子\(a''\)都除掉即可
\[答案 = (x_0' \bmod a'' + a'') \bmod a''
\]
p.s.上述很多过程在程序中体现为重复的赋值
代码
#include<iostream>
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 d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int main(void){
int n;
cin >> n;
bool flag = 1; //记录是否无解
ll a1, m1;
cin >> a1 >> m1;
for (int i = 0; i < n - 1; i ++ ){ //每次把一个新的方程合并到现有方程之中
ll a2, m2, k1, k2;
cin >> a2 >> m2;
ll d = exgcd(a1, a2, k1, k2);
if ((m2 - m1) % d != 0){
flag = 0;
break;
} //求了之后要用的其实只有k1
k1 *= (m2 - m1) / d; //用拓欧记得要放大
ll t = a2 / d; //由于数据范围比较极限,过程中要缩小k,使k变成通解当中的最小正整数解
k1 = (k1 % t + t) % t;
m1 = a1 * k1 + m1; //刷新m
a1 = abs(a1 / d * a2); //刷新a1,使其变成a1,a2的最小公倍数,除法在前放溢出
}
if (flag){
cout << (m1 % a1 + a1) % a1 << endl;
}
else
puts("-1");
return 0;
}