一、中国剩余定理(CRT)
中国剩余定理是用来求解形如:
的方程组的定理,要求(m_1,m_2,dots m_k)两两互质。
定理如下:
设(M=prod_{i=1}^{k}m_i,M_i=frac{M}{m_i},M_it_iequiv 1pmod m_i)
那么一个满足以上方程组的解就是(x_0=sum_{i=1}^{k}a_iM_it_i),方程的通解即为(x=x_0+pM,pin Z)
证明:
对于任意(jin [1,k]),当(j ot= i)时,式子中的项(a_iM_it_iequiv 0pmod {m_j}),当(i=j)时(M_jt_jequiv 1pmod{m_j}),因此(a_jM_jt_jequiv a_jpmod {m_j}) ,因此有(sum_{i=1}^{k}a_iM_it_iequiv a_jpmod {m_j}),得证。
-
例题:洛谷模板题
sol:每个条件就相当于(xequiv b_ipmod {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 ",ans); return 0; }
-
例题:洛谷P3868
sol:(b_i|(n-a_i))意味着(nequiv a_ipmod {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 ",ans); return 0; }
二、扩展中国剩余定理(exCRT)
还是上面那个问题,但现在,我们不保证(m_i)互质了,这该怎么做?
考虑递归求解,假设我们已经求出了前(i-1)个方程的解为(xequiv x_0pmod M),其中(M=lcm(m_1,m_2,dots,m_{i-1})),那么现在我们需要求解:
那么(x=x_0+tM,tin Z)
于是(x_0+tMequiv a_ipmod {m_i}),即(tMequiv a_i-x_0pmod {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 ",ans); return 0; }