CRT&EXCRT 中国剩余定理及其扩展 EXGCD 扩展欧几里得

前言:

中国剩余定理又名孙子定理。因孙子二字歧义,常以段子形式广泛流传。

中国剩余定理并不是很好理解,我也理解了很多次。

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;
}
poj1006

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;
}
poj 2891

猜你喜欢

转载自www.cnblogs.com/Miracevin/p/9254795.html