中国剩余定理

中国剩余定理

孙子定理,又称之为中国剩余定理 ( C h i n e s e R e m a i n d e r T h e o r e m , C R T ) (Chinese Remainder Theorem, CRT) ChineseRemainderTheorem,CRT可以求解如下形式的一元线性同余方程组(其中 n 1 , n 2 , … , n k n_1,n_2,\dots,n_k n1,n2,,nk两两互质)。

{ x ≡ a 1 ( m o d   n 1 ) x ≡ a 2 ( m o d   n 2 ) ⋮ x ≡ a k ( m o d   n k ) \begin{cases}x\equiv a_1(mod\:n_1)\\x\equiv a_2(mod\:n_2)\\\quad\quad \vdots \\x\equiv a_k(mod\:n_k)\\\end{cases} xa1(modn1)xa2(modn2)xak(modnk)

中国剩余定理说明:假设整数 m 1 , m 2 , … , m n m_1,m_2,\dots,m_n m1,m2,,mn两两互质,则对任意的整数: a 1 , a 2 , … , a n a_1,a_2,\dots,a_n a1,a2,,an,方程组有解,并且我们可以用如下方式构造得到:

M = m 1 × m 2 × ⋯ × m n = ∏ i = 1 n m i M=m_1\times m_2\times\dots\times m_n=\prod_{i=1}^{n}m_i M=m1×m2××mn=i=1nmi是整数 m 1 , m 2 , … , m n m_1,m_2,\dots,m_n m1,m2,,mn的乘积,并设 M i = M m i , ∀ i ∈ { 1 , 2 , 3 , … , n } M_i=\frac{M}{m_i},\forall i\in\{1,2,3,\dots,n\} Mi=miM,i{1,2,3,,n}是除了 m i m_i mi以外的 n − 1 n-1 n1个整数的乘积。设 t i t_i ti M i M_i Mi m i m_i mi下的逆元,则方程组的通解形式为: x = a 1 t 1 M 1 + a 2 t 2 M 2 + ⋯ + a n t n M n + k M = k M + ∑ i = 1 n a i t i M i , k ∈ Z x=a_1t_1M_1+a_2t_2M_2+\dots+a_nt_nM_n+kM=kM+\sum_{i=1}^na_it_iM_i,k\in \Zeta x=a1t1M1+a2t2M2++antnMn+kM=kM+i=1naitiMi,kZ。所以在模 M M M的意义下,方程组的只有一个解: x = ( ∑ i = 1 n a i t i M i ) ( m o d   M ) x=(\sum_{i=1}^na_it_iM_i)(mod\:M) x=(i=1naitiMi)(modM)

算法流程:

  1. 计算所有模数的积 n n n;(有的上面说计算它们的最小公倍数,其实都一样它们两两互质)
  2. 对于第 i i i个方程:
    1. 计算 m i = n n i m_i=\frac{n}{n_i} mi=nin;
    2. 计算 m i m_i mi在模 n i n_i ni意义下的逆元 m i − 1 m_i^{-1} mi1
    3. 计算 C i = m i × m i − 1 C_i=m_i\times m_i^{-1} Ci=mi×mi1不要对 n i n_i ni取模);
  3. 方程组的唯一解为: a = ∑ i = 1 k a i × c i ( m o d   n ) a=\sum_{i=1}^ka_i\times c_i(mod\:n) a=i=1kai×ci(modn)

下面我们来通过一道题,加深对孙子定理的运用

猜数字

现有两组数字,每组 k k k个,第一组中的数字分别用 a 1 , a 2 , … , a k a_1,a_2,\dots,a_k a1,a2,,ak表示,第二组中的数字分别用 b 1 , b 2 , … , b k b_1,b_2,\dots,b_k b1,b2,,bk表示。其中第二组的数字是两两互素的。求最小的 n ∈ N n\in \Nu nN,满足对于 ∀ i ∈ [ i , k ] \forall i\in[i,k] i[i,k],有 b i ∣ ( n − a i ) b_i|(n-a_i) bi(nai)
输入格式
第一行一个整数 k k k
第二行 k k k个整数,表示: a 1 , a 2 , … , a k a_1,a_2,\dots,a_k a1,a2,,ak
第三行 k k k个整数,表示: b 1 , b 2 , … , b k b_1,b_2,\dots,b_k b1,b2,,bk
输出格式
输出一行一个整数,为所求的答案 n n n 1 ≤ k ≤ 10 , ∣ a i ∣ ≤ 1 0 9 , 1 ≤ b i ≤ 6 × 1 0 3 ∏ i = 1 k b i ≤ 1 0 18 1≤k≤10,∣a_i∣≤10^9,1≤b_i≤6×10^3\prod_{i=1}^k b_i\le 10^{18} 1k10ai1091bi6×103i=1kbi1018

这是孙子定理的典型运用,同样也是一道模板题,在这我我们观察到数据的范围很大,所以在计算的最后一步我们不能直接使用乘法运算,而是使用快速乘。

#include 
#include 
using namespace std;
typedef long long ll;
#define endl '\n'
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define _for(i, a, b) for (int i=(a); i<=(b); i++)
const int INF = 0x7fffffff;
const int MAXN = 1e5 + 10;

ll m[MAXN], a[MAXN], n;

void exgcd(ll a, ll b, ll &x, ll &y){
	if(b == 0){
		x = 1;
		y = 0;
		return ;
	}
	exgcd(b,a%b,x,y);
	ll temp = x;
	x = y;
	y = temp - a / b * y;
	return ;
}

ll qmul(ll n, ll a, ll mod){
	ll res = 0;
	a = a % mod;
	while(n){
		if(n & 1) res = (res + a) % mod;
		n >>= 1;
		a = (a + a) % mod;
	}
	return res % mod;
}

ll CHT(){
	ll res = 0, M = 1;
	for(int i = 1; i <= n; i++){
		M *= m[i];
	}
	for(int i = 1; i <= n; i++){
		ll x, y;
		ll Mi = M / m[i];
		exgcd(Mi,m[i],x,y);
		x = (x % m[i] + m[i]) % m[i];//防止x为负数
		res = (res + qmul(qmul(Mi,x,M),a[i],M)) % M;//使用快速乘不然有的会爆long long
	}
	if(res < 0) res += M;
	return res;
}

int main(){
	cin >> n;
	for(ll i = 1; i <= n; i++){
		cin >> a[i];
	}
	for(int i= 1; i <= n; i++){
		cin >> m[i];
	}
	for(int i = 1; i <= n; i++){
		a[i] = (a[i] % m[i] + m[i]) % m[i];
	}
	cout << CHT();
	return 0;
}

你可能感兴趣的:(数学,算法)