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

一、中国剩余定理(CRT)

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

[egin{cases} xequiv a_1pmod {m_1}\ xequiv a_2pmod {m_2}\ dots\ xequiv a_kpmod {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_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})),那么现在我们需要求解:

[egin{cases} xequiv x_0pmod M\ xequiv a_ipmod {m_i} end{cases} ]

那么(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;
    }
    
原文地址:https://www.cnblogs.com/tqxboomzero/p/14359160.html