前言:
中国剩余定理又名孙子定理。因孙子二字歧义,常以段子形式广泛流传。
中国剩余定理并不是很好理解,我也理解了很多次。
CRT 中国剩余定理
中国剩余定理,就是一个解同余方程组的算法。
求满足n个条件的最小的x。
看起来很麻烦。
先找一个特殊情况:m1,m2,...mn两两互质。
这个时候,构造M=m1*m2*...mn;
令Mi=M/mi;
所以,构造n个数,其中第i个数是除i之外的其他所有数的倍数,并且第i个数mod mi =1
即:Mi x = 1 ( mod mi ) 求出这样一个x,就求出了 这个数。
因为m之间两两互质,所以对于n个这样的方程,x本质上就是Mi在mi意义下的乘法逆元。
(不会exgcd?左转:EXGCD 扩展欧几里得)
因为互质,一定有解的。
用扩展欧几里得算就可以。
同理,构造n个数。b1,b2....bn
那么,因为bi = 1 (mod mi),所以 bi * ai = ai (mod mi)
那么,原题目中的这个x就是:x=(a1b1+a2b2+...+anbn) 验证一下,是不是?
当然,为了保证x最小,要让x和lcm做一些处理。当然,因为互质,所以lcm就是M了
x=(x%M+M)%M
例题:poj1006 生理周期 Biorhythms
在CRT上的小小变形。注意开始计算的时间d就好了。上述最小的x就是n+d
答案n=(a1b1+a2b2+a3b3-d)%lcm
#include<cstdio> #include<iostream> #include<algorithm> #include<cstdlib> using namespace std; int p,e,l; int p1,e1,l1; int tt,d; void exgcd(int a,int b,int &x,int &y){ if(b==0){ x=1,y=0;return; } exgcd(b,a%b,y,x); y-=(a/b)*x; } int main(){ exgcd(23*28,33,l1,tt); l1=(l1%33+33)%33; l1*=23*28; exgcd(28*33,23,p1,tt); p1=(p1%23+23)%23; p1*=28*33; exgcd(23*33,28,e1,tt); e1=(e1%28+28)%28; e1*=23*33; int lcm=23*28*33; int cnt=0; while(1){ ++cnt; scanf("%d%d%d%d",&p,&e,&l,&d); if(p==-1) break; int op=(p1*p+e1*e+l1*l-d+lcm)%lcm; if(op==0) op=lcm; printf("Case %d: the next triple peak occurs in %d days.\n",cnt,op); } return 0; }
EXCRT 扩展中国剩余定理
但是,不是所有的方程,m都是互质的。
m不是互质的时候,我们的Mi x = 1(mod mi) 可能就没有解了。所以挂掉。
孙子解决不了了。
但是现代人不是孙子也解决了。
(你是孙子吗)
现在,m1,m2,...mn之间没有任何关系。
只考虑两个怎么处理?
可以得到:x=a1+k1*m1 ; x=a2+k2*m2
所以 a1+k1*m1 = a2+k2*m2
k2*m2-k1*m1=a1-a2;
很像:ax+by=c
但是不是这么简单。
设m1,m2 的gcd 为 g
设a1-a2=c
当c不是g的倍数的时候,那就完了。(exgcd无解情况)
如果是,就用exgcd求出k2*m2+k1*m1=gcd(m1,m2)的k1
因为c是g的倍数,所以,两边同时乘上c/g,即k1乘上c/g
就得到了k2*m2+k1*m1=c的解k1。
当然,最好k1 再模一下m2 (k1,k2做出调整),防止爆long long
然后可以反推x,
但是注意,我们列的原方程是:k2*m2-k1*m1=c
差一个符号,所以k1 实际上是 -k1
x=-k1*m1+a1;
这样就求出了x。(可以把这个x0转化成最小的非负数解)
这个x符合第一个方程,也符合第二个方程。设这个x为x0
所以,可以得到通解是:x=x0+k*lcm(m1,m2)
满足这个条件的x就满足第一第二两个方程。满足第一第二两个方程的所有的解也都是这个方程的解。
所以第一第二个方程和这个方程是等价的。
将这个方程转化一下,可以得到新的同余方程:x=x0 (mod lcm(m1,m2))
这样,我们成功的把两个方程转化成了一个方程,以此类推。
最后留下的这个方程,它的x0的最小非负数解,就是我们要的最终答案!!!!!!!!!!!!!!
例题:poj2891
这是一个模板题,直接上代码:
#include<cstdio> #include<algorithm> #include<iostream> using namespace std; typedef long long ll; const int N=100000+10; int n; ll a[N],r[N]; ll exgcd(ll a,ll b,ll &x,ll &y){ if(b==0){ x=1,y=0;return a; } ll ret=exgcd(b,a%b,y,x); y-=(a/b)*x; return ret; } ll excrt(){ ll M=a[1],R=r[1],x,y,d; for(int i=2;i<=n;i++){ d=exgcd(M,a[i],x,y); if((R-r[i])%d) return -1; x=(R-r[i])/d*x%a[i]; R-=M*x; M=M/d*a[i]; R%=M; } return (R%M+M)%M; } int main() { while(scanf("%d",&n)!=EOF){ for(int i=1;i<=n;i++){ scanf("%lld%lld",&a[i],&r[i]); } printf("%lld\n",excrt()); } return 0; }