中国剩余定理
Chinese Remainder Theorem(CRT),中文名又称孙子定理,是唯一以“Chinese”为关键词命名的数学定理,这是我们中华民族的骄傲!
线性同余方程组
CRT 主要解决线性同余方程组的问题,在数学著作《孙子算经》卷下,有一个叫做“物不知数”问题,原文如下:
有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?
意思就是一个整数除以三余二,除以五余三,除以七余二,求这个整数。
这蕴涵着一个线性同余方程组:
其中 \(x\in\mathbb N^*\)。
我们可以拓广到一般的线性方程组,形式如下:
其中 \(x\in\mathbb N^*\)。
解法
显然,这个线性同余方程组不具有唯一解,先考虑 \(r_1,r_2\dots,r_m\) 互素的情况,这也是一般的中国剩余定理。
众所周知,模运算具有不少良好的性质,我们用到如下几个:
- 若 \(x\equiv a\ (\bmod\ c)\),且 \(y\equiv b\ (\bmod\ c)\),则 \(x+y\equiv a+b\ (\bmod\ c)\),若 \(b=0\) 为特殊情况;
- 若 \(x\equiv a\ (\bmod\ c)\),且 \(y\equiv b\ (\bmod\ c)\),则 \(xy\equiv ab\ (\bmod\ c)\),当 \(a=0\) 或 \(b=0\) 时,可得 \(xy\equiv 0\ (\bmod\ c)\)。
那么,考虑先找到线性方程组的特解 \(x'\),然后对于 \(x'\) 进行加加减减得到最终的通解,那么,我们可以让 \(x'=A_1+A_2+\dots+A_m\),其中 \(A_i\) 满足如下性质:
- \(\forall j\neq i\),\(j\in[1,m]\),\(v_j|A_i\);
- \(A_i\equiv r_i\ (\bmod\ v_i)\)。
显然,这很容易构造,我们令 \(F_i=\frac{\prod^m_{i=1}v_i}{v_i}\)。
然后易构造 \(x'=\sum_{i=1}^m{F_i}{(F_i\bmod v_i)^{-1}r_i}\) 即可,其中 \((F_i\bmod v_i)^{-1}\) 为模 \(v_i\) 意义下 \(F_i\) 的逆元。
详细的解释一下:
对于第 \(i\) 个线性同余方程 \(x\equiv r_i\ (\bmod\ v_i)\),我们考虑 \(x'\) 的每一项对于 \(v_i\) 的余数:
对于 \(j\neq i\),\({F_i}{(F_i\bmod v_i)^{-1}r_i}\bmod v_i=0\),因为根据我们的构造,\(v_i|F_j\),所以 \(v_i|{F_i}{(F_i\bmod v_i)^{-1}r_i}\);
对于 \(i\),\({F_j}{r_j}\equiv r_i\ (\bmod\ v_i)\),因为显然,根据逆元的定义,\({F_i}(F_i\bmod v_i)^{-1}\equiv 1\ (\bmod\ v_i)\),那么这样,\({F_i}{(F_i\bmod v_i)^{-1}r_i}\equiv r_i\times 1\ (\bmod\ v_i)\),显然,与 \(r_i\) 同余。
那么,\(\sum_{i=1}^m{F_i}{(F_i\bmod v_i)^{-1}r_i}\equiv 0+0+\dots+r_i+\dots+0\ (\bmod\ v_i)\),其中,第 \(i\) 项为 \(r_i\)。
代码
#include<bits/stdc++.h>
#define ll long long
#define i128 __int128
using namespace std;
const int N = 1e5+7;
ll v[N],r[N];
int phi[N];
ll qpow(ll a,ll b,ll mod) {
if(b==0) return 1;
if(b==1) return a;
ll res=qpow(a,b/2,mod);
res=res*res%mod;
if(b&1) res=res*a%mod;
return res;
}
void init() {
for(int i=1;i<N;++i) phi[i]=i;
for(int i=2;i<N;++i) {
if(phi[i]!=i) continue;
for(int j=i;j<N;j+=i)
phi[j]=phi[j]*(i-1)/i;
}
}
inline i128 inv(i128 a,i128 mod) {
return qpow(a%mod,phi[mod]-1,mod);
}
signed main() {
int n;
i128 prod=1,res=0;
init();
cin>>n;
for(int i=1;i<=n;++i) cin>>v[i]>>r[i],prod*=v[i];
for(int i=1;i<=n;++i) {
r[i]%=v[i];
i128 cur=prod/v[i];
i128 iv=inv(cur,v[i]);
res+=iv*r[i]*cur;
res%=prod;
} cout<<(ll)(res)<<'\n';
return 0;
}