中国剩余定理/扩展中国剩余定理 学习笔记
一、中国剩余定理(CRT)
中国剩余定理是用来求解形如:
的方程组的定理,要求\(m_1,m_2,\dots m_k\)两两互质。
定理如下:
设\(M=\prod_{i=1}^{k}m_i,M_i=\frac{M}{m_i},M_it_i\equiv 1\pmod {m_i}\)
那么一个满足以上方程组的解就是\(x_0=\sum_{i=1}^{k}a_iM_it_i\),方程的通解即为\(x=x_0+pM,p\in Z\)
证明:
对于任意\(j\in [1,k]\),当\(j\not= i\)时,式子中的项\(a_iM_it_i\equiv 0\pmod {m_j}\),当\(i=j\)时\(M_jt_j\equiv 1\pmod{m_j}\),因此\(a_jM_jt_j\equiv a_j\pmod {m_j}\) ,因此有\(\sum_{i=1}^{k}a_iM_it_i\equiv a_j\pmod {m_j}\),得证。
-
例题:洛谷模板题
sol:每个条件就相当于\(x\equiv b_i\pmod {a_i}\),于是上模板即可,注意不能直接用费马小定理求逆元(只能在模数是质数的时候用),要用扩欧。
code:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=11; int n,a[N],b[N]; ll M=1; inline void exgcd(ll a,ll b,ll &x,ll &y){ if(!b){x=1;y=0;return ;} exgcd(b,a%b,x,y); ll t=x;x=y;y=t-a/b*y; } inline int inv(ll a,ll mod){ ll x,y; exgcd(a,mod,x,y); return (x%mod+mod)%mod; } int main(){ scanf("%d",&n); ll ans=0; for(int i=1;i<=n;++i){ scanf("%d%d",&a[i],&b[i]); M*=a[i]; } for(int i=1;i<=n;++i){ ll mi=M/a[i],t=inv(mi,a[i]); ans=(ans+mi*t%M*b[i]%M)%M; } printf("%lld\n",ans); return 0; }
-
例题:洛谷P3868
sol:\(b_i|(n-a_i)\)意味着\(n\equiv a_i\pmod {b_i}\),于是就是模板了。注意乘法会爆\(long\ long\),于是这里使用了\(\mathcal O(1)\)快速乘。
code:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=11; int n,a[N],b[N]; ll M=1; inline void exgcd(ll a,ll b,ll &x,ll &y){ if(!b){x=1;y=0;return ;} exgcd(b,a%b,x,y); ll t=x;x=y;y=t-a/b*y; } inline ll inv(ll a,ll mod){ ll x,y; exgcd(a,mod,x,y); return (x%mod+mod)%mod; } inline ll mul(ll x,ll y,ll mod){ return (x*y-(ll)((long double)x/mod*y)*mod+mod)%mod; } int main(){ scanf("%d",&n); ll ans=0; for(int i=1;i<=n;++i) scanf("%d",&a[i]); for(int i=1;i<=n;++i){ scanf("%d",&b[i]); M*=b[i]; } for(int i=1;i<=n;++i){ ll mi=M/b[i],t=inv(mi,b[i]); ans=(ans+mul(mul(mi,t,M),a[i],M))%M; } printf("%lld\n",ans); return 0; }
二、扩展中国剩余定理(exCRT)
还是上面那个问题,但现在,我们不保证\(m_i\)互质了,这该怎么做?
考虑递归求解,假设我们已经求出了前\(i-1\)个方程的解为\(x\equiv x_0\pmod M\),其中\(M=lcm(m_1,m_2,\dots,m_{i-1})\),那么现在我们需要求解:
那么\(x=x_0+tM,t\in Z\)
于是\(x_0+tM\equiv a_i\pmod {m_i}\),即\(tM\equiv a_i-x_0\pmod {m_i}\),这是同余方程的形式,于是我们直接扩展欧几里得求出\(t\),进而得到\(x\),然后更新\(M\),继续递归求解。总复杂度是\(\mathcal O(nlog(n))\)的,稍逊于\(CRT\),注意求解过程中如果有任何一个同余方程无解,那么方程组就无解。
-
例题:洛谷模板题
这道模板题保证了有解,但乘法运算可能会爆\(long\ long\),依然要使用快速乘。
code:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e5+10; int n; ll M=0,a[N],m[N]; inline void exgcd(ll a,ll b,ll &x,ll &y){ if(!b){x=1;y=0;return ;} exgcd(b,a%b,x,y); ll t=x;x=y;y=t-a/b*y; } inline ll mul(ll x,ll y,ll mod){ return (x*y-(ll)((long double)x/mod*y)*mod+mod)%mod; } inline ll gcd(ll x,ll y){return !y?x:gcd(y,x%y);} inline ll lcm(ll x,ll y){return x/gcd(x,y)*y;} inline void exCRT(ll a,ll b,ll &x,ll &y,ll c,ll mod){ ll g=gcd(a,b),t=c/g; exgcd(a,b,x,y); x=mul(x,t,mod),y=mul(y,t,mod); } int main(){ scanf("%d",&n); ll ans=0; for(int i=1;i<=n;++i){ scanf("%lld%lld",&m[i],&a[i]); if(!M) M=m[1],ans=a[1]; else{ ll nM=lcm(M,m[i]),x,y; exCRT(M,m[i],x,y,(a[i]-ans+nM)%nM,nM); ans=(mul(M,x,nM)+ans)%nM; M=nM; } } printf("%lld\n",ans); return 0; }