中国剩余定理/扩展中国剩余定理 学习笔记

一、中国剩余定理(CRT)

中国剩余定理是用来求解形如:

\[\begin{cases} x\equiv a_1\pmod {m_1}\\ x\equiv a_2\pmod {m_2}\\ \dots\\ x\equiv a_k\pmod {m_k}\\ \end{cases} \]

的方程组的定理,要求\(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})\),那么现在我们需要求解:

\[\begin{cases} x\equiv x_0\pmod M\\ x\equiv a_i\pmod {m_i} \end{cases} \]

那么\(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;
    }
    
posted @ 2021-02-01 21:33  cjTQX  阅读(142)  评论(0编辑  收藏  举报