中国剩余定理(解一元线性同余方程组)

问题:

  在《孙子算经》中有这样一个问题:“今有物不知其数,三三数之剩二(除以3余2),五五数之剩三(除以5余3),七七数之剩二(除以7余2),问物几何?”这个问题称为“孙子问题”,该问题的一般解法国际上称为“中国剩余定理”。

解析:

  题目意思即为有这样一组方程:

(m1---mn两两互质)

 设:

 设:

 设:  所以:

 所以方程有解为:

 查找modM意义下,或最小正整数解:方程有唯一解:

注释:假设整数m1,m2,m3...mn两两互质,则对于任意整数a1,a2,a3...an,x有解

例:

对于

x % 3 = 2

x % 5 = 3

x % 7 = 2

我们需要构造一个答案         //inv为求逆元

5*7*inv(5*7,  3) % 3  =  1

3*7*inv(3*7,  5) % 5  =  1

3*5*inv(3*5,  7) % 7  =  1 

然后两边同乘你需要的数,得

2 * 5*7*inv(5*7,  3) % 3  =  2

3 * 3*7*inv(3*7,  5) % 5  =  3

2 * 3*5*inv(3*5,  7) % 7  =  2

令 

a = 2 * 5*7*inv(5*7,  3) 

b = 3 * 3*7*inv(3*7,  5) 

c = 2 * 3*5*inv(3*5,  7) 

那么

a % 3 = 2

b % 5 = 3

c % 7 = 2

其实答案就是a+b+c

因为

a%5 = a%7 = 0 //a是5*7*inv(5*7,3)的倍数,所以模5和7都是

b%3 = b%7 = 0 //同理,b,c模3/7,3/5都是0

c%3 = c%5 = 0 

所以

(a + b + c) % 3 = (a % 3) + (b % 3) + (c % 3) = 2 + 0 + 0 = 2

(a + b + c) % 5 = (a % 5) + (b % 5) + (c % 5) = 0 + 3 + 0 = 3

(a + b + c) % 7 = (a % 7) + (b % 7) + (c % 7) = 0 + 0 + 2 = 2

即a+b+c是一个满足条件得答案      //最小得正整数解为(a+b+c)%105(105=3*5*7,两两互质,105为最小公倍数)

代码:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>
#include <cstdio>
#include <queue>
#include <cmath>
#include <map>
#include <set>

using namespace std;

typedef long long ll;

const int maxn=1e5+10;

ll a[maxn],m[maxn],n;

ll ex_gcd(ll a,ll b,ll &x,ll &y){
    if(b==0){x=1;y=0;return a;}
    ll ans=ex_gcd(b,a%b,x,y);
    ll temp=x;
    x=y;
    y=temp-a/b*y;
    return ans;
}
ll inv(ll a,ll b){   //求逆
   ll x,y;
   ll ans=ex_gcd(a,b,x,y);
   if(ans!=1)return -1;
   if(x<0)x=(x%b+b)%b;
   return x;
}
ll China(){//中国剩余定理
   ll M=1;
   for(int i = 0;i<n;i++){
      M*=m[i];
   }
   ll sum=0;
   for(int i=0;i<n;i++){
      ll res=M/m[i];
      sum=(sum+a[i]*res*inv(res,m[i]))%M;
   }
   return sum;
}

int main(){
    while(scanf("%lld",&n)!=EOF){
        for(int i=0;i<n;i++){
            scanf("%lld%lld",&m[i],&a[i]);
        }
        ll ans=China();
        printf("%lld\n",ans);
    }
    return 0;
}

上述方法只能解决m两两互质的同余方程组,如果不互质呢?

法一:

假设只有两个方程,如果能用一个方程代替两个方程,并能得到这个方程的解,那么这个问题就解决了:

x=a1+ k1*b1 (x%b1= a1)

x=a2+ k2*b2 (x%b2 = a2)

则: a1l+ k1*b1= a2 + k2*b2

then: b1* k1 + b2*(-k2)= a2-a1

假设gcd(b1,b2)= g若(a2 - a1)%g!=0 :无解

否则:k1*b1= (a2-a1) + k2*b2

then: k1*b1/g = (a2-a1)/g + k2*b2/g

then: k1*b1/g = (a2-a1)/g (mod b2/g)

then : k1 = inv(b1/g,b2/g)*(a2-a1)/g (mod b2/g)

then:x = a1 + inv(b1/g,b2/g)*(a2-a1)/g*b1 + b2*b1/g*y

then :x = c(mod m)

中:c= a1 + inv(b1/g,b2/g)*(a2-a1)/g*b1

m= b1*b2/g = lcm(b1,b2)

当作一个新方程继续与下一个联立,直到剩下最后一个方程xn = cn(mod mn)则,

任意一个xn都是满足条件的解,取最小正整数即可ans = (cn%mn + mn)%mn

代码:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>
#include <cstdio>
#include <queue>
#include <cmath>
#include <map>
#include <set>

using namespace std;

typedef long long ll;
const int maxn=1e5+10;
ll C[maxn],M[maxn];
ll n,k;
ll gcd(ll a,ll b){
    return b==0?a:gcd(b,a%b);
}
ll ex_gcd(ll a,ll b,ll &x,ll &y){
    if(b==0){x=1;y=0;return a;}
    ll g=ex_gcd(b,a%b,x,y);
    ll temp=x;
    x=y;
    y=temp-a/b*y;
    return g;
}
ll inv(ll a,ll mod){
    ll X,Y;
    ll g=ex_gcd(a,mod,X,Y);
    if(g!=1)return -1;
    return (X%mod+mod)%mod;
}
/*
ll mul(ll a,ll b,ll mod){   //快速乘法
    ll ans=0;
    while(b){
        if(b&1)ans=(ans%mod+a%mod)%mod;
        b>>=1;
        a=(a%mod+a%mod)%mod;
    }
    return ans;
}
*/
int main(){
    while(scanf("%lld",&n)!=EOF){
        for(int i = 0;i<n;i++){
            scanf("%lld%lld",&M[i],&C[i]);
        }
        bool flag=true;
        for(int i=1;i<n;i++){
            ll M1=M[i-1],M2=M[i],C1=C[i-1],C2=C[i];
            ll g=gcd(M1,M2);
            if((C2-C1)%g){flag = false;break;}
            M[i]=M1/g*M2;   //可能会爆
            ll INV=inv(M1/g,M2/g);
            if(INV==-1){flag=false;break;}
            C[i]= C1+(INV*((C2-C1)/g))%(M2/g)*M1;
            C[i]=(C[i]%M[i]+M[i])%M[i];
        }
        if(!flag)printf("-1\n");
        else printf("%lld\n",C[n-1]);
    }
    return 0;
}

法二:(仅限m比较小的时候/牛客2019夏多校1-d)

注意构造前i个方程的最小自然数解,假设前i-1个方程的最小自然数解为f(i-1)。lcm(p1,p2,……,pi-1)=y,令f(i)=f(i-1)然后让f(i)每次加y,知道满足第i个方程即可。可以证明在保证有解的情况下,加法运算不会超过pi次。(所以要先判断方程组有无解)

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>
#include <cstdio>
#include <queue>
#include <cmath>
#include <map>
#include <set>

using namespace std;

typedef long long ll;
const int maxn=100+10;
ll ans,n,a[maxn],p[maxn],base,M;

const char *definitely_lie="he was definitely lying";
const char *probably_lie="he was probably lying";

int main(){
    scanf("%lld%lld",&n,&M);
    for(int i=0;i<n;i++)scanf("%lld%lld",&p[i],&a[i]);
    for(int i=0;i<n;i++){
        for(int j=i+1;j<n;j++){
            ll d=__gcd(p[i],p[j]);
            if(d!=1){
                if(a[i]%d!=a[j]%d){
                    puts(definitely_lie);
                    return 0;
                }
            }
        }
    }
    ans=0;
    base=1;
    for(int i=0;i<n;i++){
        while(ans%p[i]!=a[i]){
            ans+=base;
            if(ans>M){
                puts(probably_lie);
                return 0;
            }
        }
        ll gg=p[i]/__gcd(base,p[i]);
        if(M/base>=gg)base*=gg;
        else{
            for(int j=i+1;j<n;j++)if(ans%p[j]!=a[j]){
                puts(probably_lie);
                return 0;
            }
            printf("%lld\n",ans);
            return 0;
        }
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/wz-archer/p/11859229.html
今日推荐