【数论】中国剩余定理与扩展中国剩余定理详解

Part. 0 前置知识

  • 扩展欧几里得算法;
  • 模运算相关知识。

Part. 1 中国剩余定理

中国剩余定理是用于求解形如:

{ 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} xa1(modm1)xa2(modm2)xan(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) timiM1(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=1naimiMti

它的通解形式为:

x = x 0 + k ⋅ M x=x_0+k\cdot M x=x0+kM

特别的,这个方程的最小非负整数解为 ( 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) aimiMti0(modmk)

由于 t i ⋅ M m i ≡ 1 ( m o d    m i ) t_i\cdot \frac{M}{m_i}\equiv 1(\mod m_i) timiM1(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) aimiMtiai(modmi)

故将这个特解代入到每个方程里面去都是成立的。

Part. 2 扩展中国剩余定理

扩展中国剩余定理是用于求解形如:

{ 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} xa1(modm1)xa2(modm2)xan(modmn)

的同余方程组,其中对 m 1 , m 2 , … , m n m_1,m_2,\ldots,m_n m1,m2,,mn没有任何要求。

我们假设已经将前面 k − 1 k-1 k1个同余方程合并了,并得到一个特解 x 0 x_0 x0

M = ∏ i = 1 k − 1 m i M=\prod_{i=1}^{k-1}m_i M=i=1k1mi,考虑与第 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=Mp+x0x=mkq+ak

简单地让两个式子相等起来:

m k ⋅ q + a k = M ⋅ p + x 0 m_k\cdot q+a_k=M\cdot p+x_0 mkq+ak=Mp+x0

移一下项:

M ⋅ p − m k ⋅ q = a k − x 0 M\cdot p-m_k\cdot q=a_k-x_0 Mpmkq=akx0

然后套用扩展欧几里得算法解这个方程:

gcd ⁡ ( M , m k ) ∤   ( a k − x 0 ) \gcd(M,m_k) \not|\ (a_k-x_0) gcd(M,mk) (akx0)则整个方程组无解。

否则我们就选取 p p p的最小非负整数解(为了避免精度爆炸),这时我们得到将两个方程合并后的新的特解 x = M ⋅ p + x 0 x=M\cdot p+x_0 x=Mp+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,,mk1)

Part. 3 模板题

中国剩余定理

题目来源:洛谷-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(na1)b2(na2)bk(nak)

将这些化成同余方程组的形式:

{ 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} na10(modb1)na20(modb2)nak0(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} na1(modb1)na2(modb2)nak(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;
}

你可能感兴趣的:(#,数论)