中国剩余定理是用于求解形如:
{ x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 ) … x ≡ a n ( m o d m n ) \begin{cases} x\equiv a_1(\mod m_1)\\ x\equiv a_2(\mod m_2)\\ \ldots\\ x\equiv a_n(\mod m_n) \end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧x≡a1(modm1)x≡a2(modm2)…x≡an(modmn)
的同余方程组,其中要求 m 1 , m 2 , … , m n m_1,m_2,\ldots,m_n m1,m2,…,mn两两互质。
考虑用构造的方法去解决这个问题。
令 M = ∏ i = 1 n m i M=\prod_{i=1}^n m_i M=∏i=1nmi。再令 t i t_i ti为同余方程 t i ⋅ M m i ≡ 1 ( m o d m i ) t_i\cdot \frac{M}{m_i}\equiv 1(\mod m_i) ti⋅miM≡1(modmi)的最小非负整数解,那么就有特解:
x 0 = ∑ i = 1 n a i ⋅ M m i ⋅ t i x_0=\sum_{i=1}^{n}a_i\cdot\frac{M}{m_i}\cdot t_i x0=i=1∑nai⋅miM⋅ti
它的通解形式为:
x = x 0 + k ⋅ M x=x_0+k\cdot M x=x0+k⋅M
特别的,这个方程的最小非负整数解为 ( x 0 m o d M + M ) m o d M (x_0\mod M+M)\mod M (x0modM+M)modM。
接下来说明这个特解是成立的:
【这个部分参考自niiick的博客】我实在太菜了连这东西都推不出来了QAQ…
因为 M m i \frac{M}{m_i} miM是除了 m i m_i mi之外的所有的 m i m_i mi的倍数,所以, ∀ k ≠ i \forall k\neq i ∀k=i, a i ⋅ M m i ⋅ t i ≡ 0 ( m o d m k ) a_i\cdot\frac{M}{m_i}\cdot t_i\equiv 0(\mod m_k) ai⋅miM⋅ti≡0(modmk)。
由于 t i ⋅ M m i ≡ 1 ( m o d m i ) t_i\cdot \frac{M}{m_i}\equiv 1(\mod m_i) ti⋅miM≡1(modmi),我们乘上 a i a_i ai得到: a i ⋅ M m i ⋅ t i ≡ a i ( m o d m i ) a_i\cdot\frac{M}{m_i}\cdot t_i\equiv a_i(\mod m_i) ai⋅miM⋅ti≡ai(modmi)。
故将这个特解代入到每个方程里面去都是成立的。
扩展中国剩余定理是用于求解形如:
{ x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 ) … x ≡ a n ( m o d m n ) \begin{cases} x\equiv a_1(\mod m_1)\\ x\equiv a_2(\mod m_2)\\ \ldots\\ x\equiv a_n(\mod m_n) \end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧x≡a1(modm1)x≡a2(modm2)…x≡an(modmn)
的同余方程组,其中对 m 1 , m 2 , … , m n m_1,m_2,\ldots,m_n m1,m2,…,mn没有任何要求。
我们假设已经将前面 k − 1 k-1 k−1个同余方程合并了,并得到一个特解 x 0 x_0 x0。
令 M = ∏ i = 1 k − 1 m i M=\prod_{i=1}^{k-1}m_i M=∏i=1k−1mi,考虑与第 k k k个方程合并。我们通过同余式的性质可以将这两个方程等价转化为:
{ x = M ⋅ p + x 0 x = m k ⋅ q + a k \begin{cases} x=M\cdot p+x_0\\ x=m_k\cdot q+a_k \end{cases} { x=M⋅p+x0x=mk⋅q+ak
简单地让两个式子相等起来:
m k ⋅ q + a k = M ⋅ p + x 0 m_k\cdot q+a_k=M\cdot p+x_0 mk⋅q+ak=M⋅p+x0
移一下项:
M ⋅ p − m k ⋅ q = a k − x 0 M\cdot p-m_k\cdot q=a_k-x_0 M⋅p−mk⋅q=ak−x0
然后套用扩展欧几里得算法解这个方程:
若 gcd ( M , m k ) ∤ ( a k − x 0 ) \gcd(M,m_k) \not|\ (a_k-x_0) gcd(M,mk)∣ (ak−x0)则整个方程组无解。
否则我们就选取 p p p的最小非负整数解(为了避免精度爆炸),这时我们得到将两个方程合并后的新的特解 x = M ⋅ p + x 0 x=M\cdot p+x_0 x=M⋅p+x0。
于是递推计算下去就可以了。
特别注意: 在实际应用中,为避免精度爆炸,我们可以等价地令 M = lcm ( m 1 , m 2 , … , m k − 1 ) M=\text{lcm}(m_1,m_2,\ldots,m_{k-1}) M=lcm(m1,m2,…,mk−1)。
题目来源:洛谷-P3868-[TJOI2009]猜数字
简单分析题意可以得到:
{ b 1 ∣ ( n − a 1 ) b 2 ∣ ( n − a 2 ) … b k ∣ ( n − a k ) \begin{cases} b_1|(n-a_1)\\ b_2|(n-a_2)\\ \ldots\\ b_k|(n-a_k) \end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧b1∣(n−a1)b2∣(n−a2)…bk∣(n−ak)
将这些化成同余方程组的形式:
{ n − a 1 ≡ 0 ( m o d b 1 ) n − a 2 ≡ 0 ( m o d b 2 ) … n − a k ≡ 0 ( m o d b k ) \begin{cases} n-a_1\equiv 0(\mod b_1)\\ n-a_2\equiv 0(\mod b_2)\\ \ldots\\ n-a_k\equiv 0(\mod b_k) \end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧n−a1≡0(modb1)n−a2≡0(modb2)…n−ak≡0(modbk)
移项过去:
{ n ≡ a 1 ( m o d b 1 ) n ≡ a 2 ( m o d b 2 ) … n ≡ a k ( m o d b k ) \begin{cases} n\equiv a_1(\mod b_1)\\ n\equiv a_2(\mod b_2)\\ \ldots\\ n\equiv a_k(\mod b_k) \end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧n≡a1(modb1)n≡a2(modb2)…n≡ak(modbk)
又由于题目已经给定了 b i b_i bi是两两互质的。我们不难发现这是一个符合中国剩余定理的同余方程组,于是直接套板。
直接套用模板是不够的,注意题目中输入的数字可能是负数,且还要乘炸long long
。
细节还挺多的。
#include
#include
using namespace std;
typedef long long ll;
const int Maxn = 10;
int N;
ll A[Maxn + 5], M[Maxn + 5];
ll ExGCD(ll a, ll b, ll &x, ll &y) {
if(b == 0) {
x = 1, y = 0;
return a;
}
ll g = ExGCD(b, a % b, y, x);
y -= a / b * x;
return g;
}
ll SlowMul(ll x, ll y, ll mod) {
ll ret = 0;
while(y) {
if(y & 1) ret = (ret + x) % mod;
x = (x + x) % mod;
y >>= 1;
}
return ret;
}
ll CRT() {
ll m = 1, x0 = 0;
for(int i = 1; i <= N; i++)
A[i] = (A[i] % M[i] + M[i]) % M[i];
for(int i = 1; i <= N; i++)
m *= M[i];
for(int i = 1; i <= N; i++) {
ll x, y;
ExGCD(m / M[i], M[i], x, y);
x = (x % M[i] + M[i]) % M[i];
x0 = (x0 + SlowMul(x * (m / M[i]), A[i], m)) % m;
}
return x0;
}
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
scanf("%d", &N);
for(int i = 1; i <= N; i++)
scanf("%lld", &A[i]);
for(int i = 1; i <= N; i++)
scanf("%lld", &M[i]);
printf("%lld\n", CRT());
return 0;
}
题目来源:洛谷-P4777-【模板】扩展中国剩余定理(EXCRT)
由于这道题要炸long long
,故程序中用了龟速乘来解决这个问题。其实还可以用更为高端的快速乘,但我太菜了 不想打 。。。
#include
#include
using namespace std;
typedef long long ll;
const int Maxn = 1e5;
int N;
ll A[Maxn + 5], M[Maxn + 5];
ll ExGCD(ll a, ll b, ll &x, ll &y) {
if(b == 0) {
x = 1, y = 0;
return a;
}
ll g = ExGCD(b, a % b, y, x);
y -= a / b * x;
return g;
}
inline ll SlowMul(ll x, ll y, ll mod) {
if(y < 0) x = -x, y = -y;
ll ret = 0;
while(y) {
if(y & 1) ret = (ret + x) % mod;
x = (x + x) % mod;
y >>= 1;
}
return ret;
}
ll ExCRT() {
ll m = M[1], x0 = A[1];
for(int i = 2; i <= N; i++) {
ll x, y;
ll g = ExGCD(m, M[i], x, y);
if((A[i] - x0) % g) return -1;
ll tmp = M[i] / g;
x = (SlowMul(x, (A[i] - x0) / g, tmp)+ tmp) % tmp;
x0 += m * x;
m = m / g * M[i], x0 %= m;
}
return x0;
}
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
scanf("%d", &N);
for(int i = 1; i <= N; i++)
scanf("%lld %lld", &M[i], &A[i]);
printf("%lld\n", ExCRT());
return 0;
}