问题:
在《孙子算经》中有这样一个问题:“今有物不知其数,三三数之剩二(除以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次。(所以要先判断方程组有无解)












