zoukankan      html  css  js  c++  java
  • 中国剩余定理及其拓展

    中国剩余定理及其拓展

    中国剩余定理CRT引例:(选自孙子兵法)

    今有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二,问物几何?

    怎么考虑这个问题?

    按照题意:

    设答案为x,则有

    x≡2(mod 3)

    x≡3(mod 5)

    x≡2(mod 7)

    就是求x的最小值

    不难发现线性同余方程组的定义就是形如:

    x≡a1(mod m1)

    x≡a2(mod m2)

    x≡a3(mod m3)

    ………………………….

    x≡ak(mod mk)

    的方程

    Sol:

    先看看解法:我们再来想想为什么是正确的。

    l  Step1:从5和7的公倍数中找出一个数n1使得n1≡1(mod 3)可知n1(min)=70

    l  Step2:从3和7的公倍数中找出一个数n2使得n2≡1(mod 5)可知n2(min)=21

    l  Step3:从3和5的公倍数中找出一个数n3使得n3≡1(mod 7)可知n3(min)=15

    l  Step4:令Ans‘=a1 * n1 + a2 * n2 + a3 * n3 = 2 * 70 + 3 * 21 + 15 * 2 = 233

    l  Step5:答案Ans(min)=Ans’%lcm(3,5,7)=233%105=23

    正确性证明:

    我们试图说明这样解法的正确性。

    首先我们先证明几个引理。

    引理1:若有a≡c(mod b)则有a+kb≡c(mod b)k∈Z

                  转化一下:若a % b = c那么必有(a+k*b)% b = c

               证明是及其简单的:等式右边 =(a+k*b)% b = a % b+(kb)% b = a%b=等式左边

                  这句话翻译为人话是这样的:如果一个除法运算的余数为c,那么被除数与k倍的除数相加(或相减)的和(差)再与除数相除,余数不变。

    引理2:若a≡c(mod b)则有 (a*k) ≡ kc(mod b)k∈Z

            转化一下:a%b=c那么( a*k ) % b=k*c 其中k∈Z

    证明也是显然的:( a*k ) % b = a%b +a%b +….+a%b=c+c+……+c=k*c k∈N*

    我们现在来考虑孙子怎么考虑这个问题。

    我们设置:

    l  n1是满足除以3余2的一个数,就是n1=3k+2 其中k∈N

    l  n2是满足除以5余3的一个数,就是n2=5k+3 其中k∈N

    l  n3是满足除以7余2的一个数,就是n3=7k+2 其中k∈N

    (显然此k非彼k)

    由引理1可知:

    如果n2是3的倍数那么要使n1+n2 满足 除以3余2那么显然n1需要是3的倍数。

    按照这个道理现在n1+n2已经除以3余2要想n1+n2+n3除以 3 余2那么n3必须是3的倍数

    于是我们可以推出这样三点:

    1. 要使n1+n2+n3 满足 除以3余2,n2和n3必须是3的倍数
    2. 要使n1+n2+n3 满足 除以5余3,n1和n3必须是5的倍数
    3. 要使n1+n2+n3 满足 除以7余2,n2和n3必须是7的倍数

    再次结合上面对于n1,n2,n3的定义

    1. n1是满足除以3余2的一个数,就是n1=3k+2 其中k∈N
    2. n2是满足除以5余3的一个数,就是n1=5k+3 其中k∈N
    3. n3是满足除以7余2的一个数,就是n1=7k+2 其中k∈N

    可知

    1. n1除以3余2,且是5和7的公倍数。
    2. n2除以5余3,且是3和7的公倍数。
    3. n3除以7余2,且是3和5的公倍数。

    我们如果找出n1,n2,n3那么这个问题就解决了。

    我们设 n1=5*7*k=35k,

    解同余方程35k≡2(mod 3)那就是求35k‘≡1(mod 3)的两倍

    显然就是35关于3的逆元就可以求出k‘,

    就可以求出k=2k‘,从而求出n1=5*7*2*k’

    如此类推就可以知道n2和n3 的值

    那么就是n2=3*7*3*k‘, n3=3*5*2*k‘

    然后求出Ans’=n1+n2+n3,(只是一个解而不是最小解)

    然后我们要用到引理1了:若a % b = c那么必有(a+k*b)% b = c

    若k为负数,那么我们不停的减去lcm(3,5,7)就可以得出最优解。

    Ans= Ans’ % lcm(3,5,7)

    总结

    好了我们总结一下方法

    解线性同余方程:

    x≡a1(mod m1)

    x≡a2(mod m2)

    x≡a3(mod m3)

    ………………………….

    x≡ak(mod mk)

    其中满足{x|x=mi,i=1,2,3…k} ⊆{Prime}

    的方法:

    上面是一个特例,由于我们要求逆元inv那么必须所有的mi都要是质数,其中满足{x|x=mi,i=1,2,3…k} ⊆{Prime},多少有很大的不实用性,由于我们有扩展欧几里得算法

    可以求出ax+by=gcd(a,b)的最小正整数解,这就要用到中国剩余定理扩展。

    中国剩余定理扩展EXCRT —— 求解模数不互质情况下的线性方程组

    其实比上面更加简单。

    我们考虑两个同余方程:

    x≡a1(mod m1)

    x≡a2(mod m2)

    必然可以表示成这样的形式

                                  x=a1+m1x1

                                  x=a2+m2x2

    联立可知:    a1 + m1x1 = x = a2 + m2x2

    那么这里x1和x2是未知数,就是

    m1x1-m2x2=a2-a1

    可以解出来x1(最小),带回到 x的一个特解x‘

    这个x’显然可以满足

    x≡a1(mod m1)

    x≡a2(mod m2)

    然后组成这样的新的同余方程

                                  x≡x‘(mod lcm(m1,m2))

    然后就可以解出最终解出x

    模板题: https://www.luogu.org/problemnew/show/P4777

    代码:

    # include <bits/stdc++.h>
    # define LL long long
    using namespace std;
    const int MAXN=1e5+1;
    LL m[MAXN],a[MAXN];
    int n;
    inline LL read()
    {
        LL X=0,w=0; char c=0;
        while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
        while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
        return w?-X:X;
    }
    inline LL ex_gcd(LL a,LL b,LL &x,LL &y)
    {
        if (!b) { x=1,y=0; return a;}
        LL g=ex_gcd(b,a%b,x,y);
        LL t=x;x=y;y=t-a/b*y;
        return g;
    }
    inline LL gcd(LL a,LL b){ return (!b)?a:gcd(b,a%b);    }
    inline LL lcm(LL a,LL b){ LL g=gcd(a,b); return a/g*b; }
    inline LL mul(LL M,LL N,LL MO)
    {
        if (!N) return 0ll;
        LL t=mul(M,N>>1,MO)%MO;
        t=t+t%MO;
        if (N%2==1) t=t+M%MO;
        return t;
    }
    inline LL EXCRT()
    {
        LL x,y,k,a2,m2;
        LL a1=a[1],m1=m[1];
        for (int i=2;i<=n;i++) {
            a2=a[i],m2=m[i];
            LL aa=m1,bb=m2,cc=((a2-a1)%bb+bb)%bb;
            LL g=ex_gcd(aa,bb,x,y),bg=bb/g;
            if (cc%g!=0) return -1;
            LL k=cc/g;
            LL xmin=(mul(x,k,bg)+bg)%bg;
            a1=a1+m1*xmin; m1=m1*bg;
            a1=(a1%m1+m1)%m1; 
        }
        return (a1%m1+m1)%m1;
    }
    int main()
    {
        scanf("%d",&n);
        for (int i=1;i<=n;i++) 
         m[i]=read(),a[i]=read();
        printf("%lld
    ",EXCRT()); 
        return 0;
    }
  • 相关阅读:
    宏观经济指标
    线程与进程 concurrent.futures模块
    python 进程和线程(2)
    进程和线程(1)
    C++学习
    原则阅读笔记
    python类(3)感悟
    python类(2)
    打新股技巧
    python连接数据库
  • 原文地址:https://www.cnblogs.com/ljc20020730/p/9734890.html
Copyright © 2011-2022 走看看