【數論】中國剩餘定理與擴展中國剩餘定理詳解

Part. 0 前置知識

  • 擴展歐幾里得算法;
  • 模運算相關知識。

Part. 1 中國剩餘定理

中國剩餘定理是用於求解形如:

{xa1(mod  m1)xa2(mod  m2)xan(mod  mn) \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}

的同餘方程組,其中要求m1,m2,,mnm_1,m_2,\ldots,m_n兩兩互質。

考慮用構造的方法去解決這個問題。

M=i=1nmiM=\prod_{i=1}^n m_i。再令tit_i爲同餘方程tiMmi1(mod  mi)t_i\cdot \frac{M}{m_i}\equiv 1(\mod m_i)的最小非負整數解,那麼就有特解:

x0=i=1naiMmiti x_0=\sum_{i=1}^{n}a_i\cdot\frac{M}{m_i}\cdot t_i

它的通解形式爲:

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

特別的,這個方程的最小非負整數解爲(x0mod  M+M)mod  M(x_0\mod M+M)\mod M


接下來說明這個特解是成立的:

【這個部分參考自niiick的博客我實在太菜了連這東西都推不出來了QAQ…

因爲Mmi\frac{M}{m_i}是除了mim_i之外的所有的mim_i的倍數,所以,ki\forall k\neq iaiMmiti0(mod  mk)a_i\cdot\frac{M}{m_i}\cdot t_i\equiv 0(\mod m_k)

由於tiMmi1(mod  mi)t_i\cdot \frac{M}{m_i}\equiv 1(\mod m_i),我們乘上aia_i得到:aiMmitiai(mod  mi)a_i\cdot\frac{M}{m_i}\cdot t_i\equiv a_i(\mod m_i)

故將這個特解代入到每個方程裏面去都是成立的。

Part. 2 擴展中國剩餘定理

擴展中國剩餘定理是用於求解形如:

{xa1(mod  m1)xa2(mod  m2)xan(mod  mn) \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}

的同餘方程組,其中對m1,m2,,mnm_1,m_2,\ldots,m_n沒有任何要求。

我們假設已經將前面k1k-1個同餘方程合併了,並得到一個特解x0x_0

M=i=1k1miM=\prod_{i=1}^{k-1}m_i,考慮與第kk個方程合併。我們通過同餘式的性質可以將這兩個方程等價轉化爲:

{x=Mp+x0x=mkq+ak \begin{cases} x=M\cdot p+x_0\\ x=m_k\cdot q+a_k \end{cases}

簡單地讓兩個式子相等起來:

mkq+ak=Mp+x0 m_k\cdot q+a_k=M\cdot p+x_0

移一下項:

Mpmkq=akx0 M\cdot p-m_k\cdot q=a_k-x_0

然後套用擴展歐幾里得算法解這個方程:

gcd(M,mk)∤ (akx0)\gcd(M,m_k) \not|\ (a_k-x_0)則整個方程組無解。

否則我們就選取pp的最小非負整數解(爲了避免精度爆炸),這時我們得到將兩個方程合併後的新的特解x=Mp+x0x=M\cdot p+x_0

於是遞推計算下去就可以了。

特別注意: 在實際應用中,爲避免精度爆炸,我們可以等價地令M=lcm(m1,m2,,mk1)M=\text{lcm}(m_1,m_2,\ldots,m_{k-1})

Part. 3 模板題

中國剩餘定理

題目來源:洛谷-P3868-[TJOI2009]猜數字

簡單分析題意可以得到:

{b1(na1)b2(na2)bk(nak) \begin{cases} b_1|(n-a_1)\\ b_2|(n-a_2)\\ \ldots\\ b_k|(n-a_k) \end{cases}

將這些化成同餘方程組的形式:

{na10(mod  b1)na20(mod  b2)nak0(mod  bk) \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}

移項過去:

{na1(mod  b1)na2(mod  b2)nak(mod  bk) \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}

又由於題目已經給定了bib_i是兩兩互質的。我們不難發現這是一個符合中國剩餘定理的同餘方程組,於是直接套板。

直接套用模板是不夠的,注意題目中輸入的數字可能是負數,且還要乘炸long long

細節還挺多的。

#include <cstdio>
#include <algorithm>
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 <cstdio>
#include <algorithm>
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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章