同余 --算法竞赛专题解析(22):数论

本系列文章将于2021年整理出版。前驱教材:《算法竞赛入门到进阶》 清华大学出版社
网购:京东 当当   作者签名书:点我
公众号同步:算法专辑   
暑假福利:胡说三国
有建议请加QQ 群:567554289

文章目录

  • 1. 同余概述
    • 1.1. 同余定义
    • 1.2. 一些定理和性质
  • 2. 一元线性同余方程
  • 3. 逆
    • 3.1.逆的概念
    • 3.2.求逆
    • 3.3. 用逆求解同余方程
    • 3.4. 逆与除法取模
  • 4. 同余方程组
    • 4.1. 中国剩余定理
    • 4.2. 迭代法

   同余是很巧妙的工具,它使得人们能够用等式的形式来简洁地描述整除关系。
   在阅读本节内容时,请对照上一节“线性丢番图方程”的内容,有很多类似的地方。

1. 同余概述

1.1. 同余定义

  设m是正整数,若a和b是整数,且 m ∣ ( a − b ) m | (a - b) m(ab),则称 a a a b b b m m m同余。也就是说, a a a除以 m m m得到的余数,和 b b b除以 m m m的余数相同;或者说, a − b a - b ab除以 m m m,余数是0。
  把 a a a b b b m m m同余记为 a ≡ b ( m o d m ) a\equiv b (mod m) ab(modm) m m m称为同余的模。例子:
  (1)因为7|(18-4),所以18 ≡ \equiv 4 (mod 7),18除以7余数是4,4除以7的余数也是4;
  (2)3 ≡ \equiv 6 (mod 9),3除以9余数是3,-6除以9的余数也是3;
  (3)13和5模9不同余,因为13除以9余数是4,5除以9余数是5。

1.2. 一些定理和性质

  (1)若 a a a b b b是整数, m m m为正整数,则 a ≡ b ( m o d   m ) a\equiv b (mod\ m) ab(mod m)当且仅当 a   m o d   m = b m o d   m a\ mod\ m = b mod\ m a mod m=bmod m
  (2)把同余式转换为等式。若 a a a b b b是整数,则 a ≡ b ( m o d   m ) a\equiv b (mod\ m) ab(mod m)当且仅当存在整数,使得 a = b + k m a = b + km a=b+km。例如:19-2 (mod 7),有19 = -2 + 3 ∙ 7。
  (3)设 m m m是正整数,模 m m m的同余满足下面的性质:
  1)自反性。若 a a a是整数,则 a ≡ a ( m o d   m ) a\equiv a (mod\ m) aa(mod m)
  2)对称性。若 a a a b b b是整数,且 a ≡ b ( m o d   m ) a\equiv b (mod\ m) ab(mod m),则 b ≡ a ( m o d   m ) b\equiv a (mod\ m) ba(mod m)
  3)传递性。若 a 、 b 、 c a、b、c abc是整数,且 a ≡ b ( m o d   m ) 和 b ≡ c ( m o d m ) a\equiv b (mod\ m)和b\equiv c (mod m) ab(mod m)bc(modm),则 a ≡ c ( m o d   m ) a\equiv c (mod\ m) ac(mod m)

2. 一元线性同余方程

  一元线性同余方程:设 x x x是未知数,给定 a 、 b 、 m a、b、m abm,求整数 x x x,满足 a x ≡ b ( m o d   m ) ax\equiv b (mod\ m) axb(mod m)
  研究线性同余方程有什么用处? a x ≡ b ( m o d   m ) ax\equiv b(mod\ m) axb(mod m)表示 a x − b ax - b axb m m m的倍数,设为 − y -y y倍,则有 a x + m y = b ax + my = b ax+my=b,这就是二元线性丢番图方程。所以,求解一元线性同余方程等同于求解二元线性丢番图方程。
  方程是否有解?如果有解,有多少解?如何求出解?与线性丢番图的定理一样,线性同余方程也有类似的定理。
  定理:设 a , b a,b ab m m m是整数, m > 0 , g c d ( a , m ) = d m > 0,gcd(a, m) = d m>0gcd(a,m)=d,若 d d d不能整除b,则 a x ≡ b ( m o d   m ) ax\equiv b (mod\ m) axb(mod m)无解,若 d d d能整除 b b b,则 a x ≡ b ( m o d   m ) ax\equiv b (mod\ m) axb(mod m) d d d个模 m m m不同余的解。
  定理的前半部分可以概况为: a x ≡ b ( m o d   m ) ax\equiv b (mod\ m) axb(mod m)有解的充分必要条件是 g c d ( a , m ) gcd(a, m) gcd(a,m)能整除b。
  定理的后半部分说明了解的情况。与线性丢番图方程类似,如果有一个特解是 x 0 x_0 x0 ,那么通解是 x = x 0 + ( m / d ) n x = x_0 + (m/d)n x=x0+(m/d)n,当 n = 0 , 1 , 2 , . . . , d − 1 n = 0, 1, 2, ..., d -1 n=0,1,2,...,d1时,有 d d d个模 m m m不同余的解。
  推论: a a a m m m互素时,因为 d = g c d ( a , m ) = 1 d = gcd(a, m)=1 d=gcd(a,m)=1,所以线性同余方程 a x ≡ b ( m o d   m ) ax\equiv b (mod\ m) axb(mod m)有唯一的模 m m m不同余的解。这个推论在下一节的逆中有应用。
  最后回到求解 a x ≡ b ( m o d   m ) ax\equiv b (mod\ m) axb(mod m)的问题:首先求逆,然后利用逆求得 x x x

3. 逆

  求解一般形式的同余方程 a x ≡ b ( m o d   m ) ax\equiv b (mod\ m) axb(mod m),需要用到逆。

3.1.逆的概念

  给定整数a,且满足 g c d ( a , m ) = 1 gcd(a, m) = 1 gcd(a,m)=1,称 a x ≡ 1 ( m o d   m ) ax\equiv 1(mod\ m) ax1(mod m)的一个解为 a a a m m m的逆。记为 a − 1 a^{-1} a1
  例如: 8 x ≡ 1 ( m o d   31 ) 8x\equiv 1(mod\ 31) 8x1(mod 31),有一个解是 x x x = 4,4是8模31的逆。所有的解,例如35、66等,都是8模31的逆。
  可以借助丢番图方程理解逆的概念, 8 x ≡ 1 ( m o d   31 ) 8x\equiv 1(mod\ 31) 8x1(mod 31)即方程 8 x + 31 y = 1 8x + 31y = 1 8x+31y=1 x x x = 4是8模31的逆,4×8-1能整除31。

3.2.求逆

  有多种方法可以求逆。
(1)扩展欧几里得求单个逆
  下面的例题是求逆,即求解同余方程 a x ≡ 1 ( m o d   m ) ax\equiv 1(mod\ m) ax1(mod m)


同余方程(求逆) 洛谷 P1082
题目描述:求关于x的同余方程 a x ≡ 1 ( m o d   m ) ax\equiv 1(mod\ m) ax1(mod m)的最小正整数解。2≤a, m≤2,000,000,000。


  题解 a x ≡ 1 ( m o d   m ) ax\equiv 1(mod\ m) ax1(mod m),即丢番图方程 a x + m y = 1 ax + my = 1 ax+my=1,先用扩展欧几里得求出 a x + m y = 1 ax + my = 1 ax+my=1的一个特解 x 0 x_0 x0,通解是 x = x 0 + m n x = x_0 + mn x=x0+mn。然后通过取模操作算最小整数解 ( ( x 0 m o d   m ) + m ) m o d   m ( (x_0 mod\ m) + m) mod\ m ((x0mod m)+m)mod m,因为 m m m > 0,可以保证结果是正整数。

long long mod_inverse(long long a, long long m){    //求逆
	long long x,y;
    extend_gcd(a,m,x,y);
    return  (x%m + m) % m;                          //保证返回最小正整数
}
int main(){
    long long a,m;  cin >> a >>m; 
    cout << mod_inverse(a,m);
    return 0;
}

(2)费马小定理求单个逆
  费马小定理:设 n n n是素数, a a a是正整数且与 n n n互质,那么有 a n − 1 ≡ 1 ( m o d   n ) a^{n-1}\equiv 1(mod\ n) an11(mod n)
   a a n − 2 ≡ 1 ( m o d   n ) a a^{n-2}\equiv 1(mod\ n) aan21(mod n),那么 a n − 2 m o d   n a^{n-2} mod\ n an2mod n就是 a a a n n n的逆。计算需要用到快速幂取模fast_pow(),参考前面章节“大素数的判定”。快速幂取模的复杂度是 O ( l o g n ) O(log n) O(logn)的。

long long mod_inverse(long long a,long long mod){
   return fast_pow(a,mod - 2,mod);           
}

(3)递推求多个逆
  如果要求1 ~ n内所有的逆,可以用递推。复杂度是O(n)的。


乘法逆元 洛谷 P3811
题目描述:给定 n , p n,p np,求 1   n 1 ~ n 1 n 中所有整数在模 p p p 意义下的乘法逆元。
1 ≤ n ≤ 3 × 1 0 6 1≤n≤3×10^6 1n3×106 n < p < 20000528 n < p < 20000528 n<p<20000528 p p p为质数。
输入:一行两个正整数 n 、 p n、p np
输出:输出 n n n行,第 i i i行表示 i i i在模 p p p下的乘法逆元。


  首先, i = 1 i=1 i=1时逆是1。下面求 i > 1 i > 1 i>1时的逆,用递推法。
  设 p / i = k p/i = k p/i=k,余数是 r r r,即 k i + r ≡ 0 ( m o d   p ) k i + r\equiv 0(mod\ p) ki+r0(mod p)
  在两边乘 i − 1 r − 1 i^{-1} r^{-1} i1r1,得到 k r − 1 + i − 1 ≡ 0 ( m o d   p ) k r^{-1} + i^{-1}\equiv 0 (mod\ p) kr1+i10(mod p)
  移项得 i − 1 ≡ − k r − 1 ( m o d   p ) i^{-1}\equiv - k r^{-1} (mod\ p) i1kr1(mod p) ,即 i − 1 ≡ − p / i r − 1 ( m o d   p ) i^{-1}\equiv - p/i r^{-1} (mod\ p) i1p/ir1(mod p) i − 1 ≡ ( p − p / i ) r − 1 ( m o d   p ) i^{-1}\equiv (p - p/i) r^{-1} (mod\ p) i1(pp/i)r1(mod p)

long long inv[maxn];
void inverse(long long n, long long p){
    inv[1]=1;
    for(int i = 2;i<maxn;i++)
       inv[i]= (p - p/i) * inv[p%i] % p;
}

  下面给出一个求逆的例题。


A/B hdu 1576
题目描述:求(A/B)%9973,但由于A很大,我们只给出n (n = A%9973)(我们给定的A必能被B整除,且gcd(B, 9973) = 1)。
输入:第一行是T,表示有T组数据。每组数据有两个数n(0 <= n < 9973)和B(1 <= B <= 10^9)。
输出:对每组数据,输出(A/B)%9973。


题解
  设答案 k = ( A / B ) k = (A/B) % 9973 k=(A/B)
   做以下变换: A / B = k + 9973 x , A = k B + 9973 x B A/B = k + 9973 x,A = kB + 9973 x B A/B=k+9973xA=kB+9973xB
   把 A A % 9973 = n A代入得 k B k B % 9973 = n kB,即 k B = n + 9973 y k B = n + 9973y kB=n+9973y
   两边除 n n n ( k / n ) B + ( − y / n ) 9973 = 1 (k/n) B + (-y/n) 9973 = 1 (k/n)B+(y/n)9973=1,这是形如 a x + b y = 1 ax + by = 1 ax+by=1的丢番图方程,即 a x ≡ 1 ( m o d   m ) ax\equiv 1(mod\ m) ax1(mod m),其中 x = k / n x = k/n x=k/n。求解逆 x x x,得到 k / n k/n k/n,再乘以 n n n,就是 k k k

3.3. 用逆求解同余方程

  逆有什么用?如果有 a a a m m m的一个逆,可以用来解如 a x ≡ b ( m o d   m ) ax\equiv b (mod\ m) axb(mod m)的任何同余方程。
   记 a − 1 a^{-1} a1 a a a的一个逆,有 a − 1 a ≡ 1 ( m o d   m ) a^{-1}a\equiv 1(mod\ m) a1a1(mod m)。在 a x ≡ b ( m o d   m ) ax\equiv b (mod\ m) axb(mod m)的两边同时乘以 a − 1 a^{-1} a1,得到 a − 1 a x ≡ a − 1 b ( m o d   m ) a^{-1}ax\equiv a^{-1}b(mod\ m) a1axa1b(mod m),即 x ≡ a − 1 b ( m o d   m ) x\equiv a^{-1}b (mod\ m) xa1b(mod m)
   例如:为了求出 8 x ≡ 22 ( m o d   31 ) 8x\equiv 22 (mod\ 31) 8x22(mod 31)的解,可以两边乘以4,4是8模31的一个逆,得 4 ∗ 8 x = 4 ∗ 22 ( m o d   31 ) 4 * 8x = 4 * 22 (mod\ 31) 48x=422(mod 31),因此 x ≡ 88 ( m o d   31 ) ≡ 26 ( m o d   31 ) x\equiv 88 (mod\ 31)\equiv 26 (mod\ 31) x88(mod 31)26(mod 31)
   定理:设 p p p是素数,正整数 a a a是其自身模 p p p的逆,当且仅当 a ≡ 1 ( m o d   p ) a\equiv 1 (mod\ p) a1(mod p) a ≡ − 1 ( m o d   p ) a\equiv -1 (mod\ p) a1(mod p)
   证明:若 a ≡ 1 ( m o d   p ) a\equiv 1 (mod\ p) a1(mod p) a ≡ − 1 ( m o d   p ) a\equiv -1 (mod\ p) a1(mod p),有 a 2 ≡ 1 ( m o d   p ) a^2\equiv 1 (mod\ p) a21(mod p),所以 a a a是其自身模 p p p的逆。反过来也成立。

3.4. 逆与除法取模

  逆的一个重要应用是求除法的模。例如在catalan数中,有这样一个需求:求 ( a / b ) m o d   m (a/b) mod\ m (a/b)mod m,即 a a a除以 b b b,然后对 m m m取模。由于这里 a a a b b b都是很大的数,做除法后再取模,会损失精度。用逆可以避免除法计算,设b的逆元是 b − 1 b^{-1} b1,有:
   ( a / b ) m o d   m = ( ( a / b ) m o d   m ) ∗ ( ( b b − 1 ) m o d   m ) = ( a / b ∗ b b − 1 ) m o d   m = ( a b − 1 ) m o d   m (a/b) mod\ m = ((a/b) mod\ m)*((bb^{-1}) mod\ m) = (a/b*bb^{-1}) mod\ m = (ab^{-1}) mod\ m (a/b)mod m=((a/b)mod m)((bb1)mod m)=(a/bbb1)mod m=(ab1)mod m
  经过上述推导,除法的模运算转换成了乘法模运算: ( a / b ) m o d   m = ( a b − 1 ) m o d   m (a/b) mod\ m = (ab^{-1}) mod\ m (a/b)mod m=(ab1)mod m
  下面是一个除法取模的例题。


Detachment http://acm.hdu.edu.cn/showproblem.php?pid=5976
题目描述:把一个整数X分成多个整数的和:x = a1 + a2 + …,且ai ≠ aj,使得s = a1 ∗a2 * …最大。
输入:第一行是T,表示测试用例数量,后面有T行,每一行一个整数表示X。1 ≤ T ≤ 10^6, 1 ≤ X ≤ 10^9。
输出:对每个用例,首先计算最大的s,然后输出它对mod = 10^9+7的取模。


题解:如何分解X,才能使积s最大?这是小学奥数题,读者可以自己举例子推理。
  首先,分解的数越多,积s越大。比如X分解为2个数,对比不分解的1个数X:(X - k)(X + k) > X。
  其次,分解的数越接近,积越大。例如分成2个数,对比X/2 - 1、X/2 +1和X/2 - k、X/2 +k,有:(X/2 - 1)(X/2 +1) > (X/2 - k)(X/2 + k)。
  结论是:把X尽量分为更多连续数的和,得到的积s最大。为了分解更多,就从2开始分解:X = 2 + 3 + 4 + 5 + …。从1开始分解不好,因为1对乘积没有贡献。如果还有余数,就把余数拆开,加到其他数上:从后往前加,每个数加上1,这样可以保证每个数都不同。例如17 = 2 + 3 + 4 + 5 + 3,最后有个余数3,把它拆成3个1,加到后面3个数上,得:17 = 2 + 4 + 5 + 6。再例如13 = 2 + 3 + 4 + 4,后面的余数4,拆成4个1,但是前面只有3个数,不够用,多的1再加在最后,得:13 = 3 + 4 + 6。
  分解有两种情况:
   (1) 2 × 3 × 4 × . . . × ( i − 1 ) × ( i + 1 ) × . . . × k × ( k + 1 ) 2×3×4×...×(i-1)×(i+1)×...×k×(k+1) 2×3×4×...×(i1)×(i+1)×...×k×(k+1),中间少个 i i i
  (2) 3 × 4 × . . . × i × ( i + 1 ) × . . . × k × ( k + 2 ) 3×4×...×i×(i+1)×...×k×(k+2) 3×4×...×i×(i+1)×...×k×(k+2),前面少个2,后面多个 k + 2 k+2 k+2
   求s的时候,先计算连续的乘积 a a a,然后除以 i i i,或者除以2乘以k+2。例如第(1)种情况, s = a / i s = a/i s=a/i,输出的结果是 ( a / i )   %   m o d (a/i)\ \%\ mod (a/i) % mod。除法取模需要用到逆。本题的模10^9 + 7正好是个素数,所以求逆用扩展欧几里得或费马小定理都行。
   下面给出编码,细节有:
   (1)先预计算出从2开始的前缀和和连续积,用于判断分解到哪个数为止。并用upper_bound()查找x的位置。
   (2)把余数加到后面的数上去,并查找缺少的 i i i
   (3)计算结果。用逆计算除法取模。

#include
using namespace std;
#define ll long long
 
const int maxnum = 1e5;             //分解的数不会超过50000个,请自己分析
const int mod = 1e9 + 7;
 
ll sum[maxnum], mul[maxnum];  //前缀和、连续积

ll fast_pow(ll x,ll y,int m){       //快速幂取模:x^y mod m
    ll res = 1;
    while(y) {
        if(y&1) res*=x, res%=m;
        x = (x*x) % m;
        y>>=1;
    }
    return res;
}

long long mod_inverse(long long a,long long mod){ //费马小定理求逆,或者用扩展欧几里得求逆
   return fast_pow(a,mod - 2,mod);           
}

void init(){       //预计算前缀和、连续积
    sum[1] = 0;  mul[1] = 1;
    for(int i=2; i<=maxnum; i++){
        sum[i] = sum[i-1] + i;      //计算前缀和
        mul[i] = (i*mul[i-1]) % mod;//计算连续积
    }
} 
 
int main(){
    init();
    int T; scanf("%d",&T);
    while(T--){
        int x; scanf("%d",&x);
        if( x == 1) {puts("1"); continue;}  //特殊情况

        int k = upper_bound(sum+1,sum+1+maxnum,x)-sum-1;  //分解成k个数
        int m = x - sum[k];    //余数
        ll ans;
        if(k==m)           
            ans = mul[k] * mod_inverse(2,mod) %mod * (k+2) % mod; //第2种情况           
        else            
            ans = mul[k+1] * mod_inverse(k-m+1,mod) % mod % mod;  //第1种情况
        printf("%lld\n",ans);
    }
    return 0;
}

4. 同余方程组

  根据上一节的讨论,同余方程 a x ≡ b ( m o d   m ) ax\equiv b (mod\ m) axb(mod m)有解时,即 g c d ( a , m ) gcd(a, m) gcd(a,m) 能整除 b b b时,可以解得 x ≡ a ′ ( m o d   m ′ ) x\equiv a' (mod\ m') xa(mod m),所以这也是同余方程的一般形式。本节讨论同余方程组的求解:
     x ≡ a 1 ( m o d   m 1 ) x\equiv a_1 (mod\ m_1) xa1(mod m1)
     x ≡ a 2 ( m o d   m 2 ) x\equiv a_2 (mod\ m_2) xa2(mod m2)
    …
     x ≡ a r ( m o d   m r ) x\equiv a_r (mod\ m_r) xar(mod mr)
  例:有一个数 x x x,被3除余2,被5除余3,被7除余2,列成同余方程就是:
     x ≡ 2 ( m o d   3 ) x\equiv 2 (mod\ 3) x2(mod 3)
     x ≡ 3 ( m o d   5 ) x\equiv 3 (mod\ 5) x3(mod 5)
     x ≡ 2 ( m o d   7 ) x\equiv 2 (mod\ 7) x2(mod 7)
  求解结果是: x = 23 + 3 × 5 × 7 × n x = 23 + 3 × 5 ×7 × n x=23+3×5×7×n n ≥ 0 n ≥ 0 n0,或者写为 x ≡ 23 ( m o d   3 × 5 × 7 ) x\equiv 23 (mod\ 3 × 5 × 7) x23(mod 3×5×7) x x x的最小正整数解是23。
  本节介绍中国剩余定理和迭代法,前者是用于 m 1 , m 2 , . . . , m r m_1,m_2,...,m_r m1m2...mr两两互素情况下的优秀解法,后者是更一般条件下的通用解法。适用中国剩余定理的方程组肯定有解,而迭代法处理的更一般情况,可能是无解的。

4.1. 中国剩余定理

  中国剩余定理1:设 m 1 , m 2 , . . . , m r m_1,m_2,...,m_r m1m2...mr是两两互素的正整数,则同余方程组
     x ≡ a 1 ( m o d   m 1 ) x\equiv a_1 (mod\ m_1) xa1(mod m1)
     x ≡ a 2 ( m o d   m 2 ) x\equiv a_2 (mod\ m_2) xa2(mod m2)
    …
     x ≡ a r ( m o d   m r ) x\equiv a_r (mod\ m_r) xar(mod mr)
  有整数解,并且模 M = m 1 m 2 . . . m r M = m_1m_2...m_r M=m1m2...mr唯一,解为:
   x = ( a 1 M 1 M 1 − 1 + a 2 M 2 M 2 − 1 + . . . + a r M r M r − 1 ) ( m o d   M ) x=(a_1M_1M_1^{-1} + a_2M_2M_2^{-1} + ... + a_rM_rM_r^{-1}) (mod\ M) x=(a1M1M11+a2M2M21+...+arMrMr1)(mod M)
  其中 M i = M / m i M_i = M/m_i Mi=M/mi M i − 1 M_i^{-1} Mi1 M i M_i Mi m i m_i mi的逆元。
   读者可以尝试自己证明。

   例题:解同余方程组 x ≡ 2 ( m o d   3 ) , x ≡ 3 ( m o d   5 ) , x ≡ 2 ( m o d   7 ) x\equiv 2 (mod\ 3),x\equiv 3 (mod\ 5),x\equiv 2 (mod\ 7) x2(mod 3)x3(mod 5)x2(mod 7)
   解题步骤:
   (1) M = 3 × 5 × 7 = 105 , M 1 = 105 / 3 = 35 , M 2 = 105 / 5 = 21 , M 3 = 105 / 7 = 15 M = 3 ×5 ×7 = 105,M1 = 105/3 = 35,M2 = 105/5 = 21,M3 = 105/7 = 15 M=3×5×7=105M1=105/3=35M2=105/5=21M3=105/7=15
   (2)求逆: M 1 − 1 = 2 , M 2 − 1 = 1 , M 3 − 1 = 1 M_1^{-1} = 2,M_2^{-1} = 1,M_3^{-1} = 1 M11=2M21=1M31=1
   (3)最后计算 x : x ≡ 2 × 35 × 2 + 3 × 21 × 1 + 2 × 15 × 1 ≡ 233 ≡ 23 ( m o d 105 ) x:x\equiv 2×35×2 + 3×21×1 + 2×15×1\equiv 233\equiv 23(mod 105) xx2×35×2+3×21×1+2×15×123323(mod105)

4.2. 迭代法

  中国剩余定理的编码很容易,但是它的限制条件是方程组的 m 1 , m 2 , . . . , m r m_1,m_2,...,m_r m1m2...mr两两互素。如果不互素,该如何解题呢?这就是迭代法。
  迭代法的思路很简单,就是每次合并两个同余式,逐步合并,直到合并完所有等式,只剩下一个,就得到了答案。
  合并的时候,把同余方程转化为等式更容易操作。这是根据同余的一个性质:若 x x x a a a是整数,则 x ≡ a ( m o d   m ) x\equiv a (mod\ m) xa(mod m)当且仅当存在整数,使得 x = a + k m x = a + km x=a+km

1、 示例
  以方程组 x ≡ 2 ( m o d   3 ) , x ≡ 3 ( m o d   5 ) , x ≡ 2 ( m o d   7 ) x\equiv 2 (mod\ 3),x\equiv 3 (mod\ 5),x\equiv 2 (mod\ 7) x2(mod 3)x3(mod 5)x2(mod 7)为例,说明合并过程。下面的计算步骤,前3步合并了第1和第2个同余式,后面3步继续合并第3个同余式。
   1)把第1个同余式 x ≡ 2 ( m o d   3 ) x\equiv 2 (mod\ 3) x2(mod 3)转换为 x = 2 + 3 t x = 2 + 3t x=2+3t,代入第2个同余式得 2 + 3 t ≡ 3 ( m o d   5 ) 2 + 3t\equiv 3 (mod\ 5) 2+3t3(mod 5)
   2)求解 2 + 3 t ≡ 3 ( m o d   5 ) 2 + 3t\equiv 3 (mod\ 5) 2+3t3(mod 5)
   首先变为 3 t ≡ ( 3 − 2 ) ( m o d   5 ) 3t \equiv (3 - 2) (mod\ 5) 3t(32)(mod 5),即 3 t ≡ 1 ( m o d   5 ) 3t \equiv 1 (mod\ 5) 3t1(mod 5),因为 g c d ( 3 , 5 ) gcd(3, 5) gcd(3,5)能整除1,所以有解。
   然后求解 3 t ≡ 1 ( m o d   5 ) 3t \equiv 1 (mod\ 5) 3t1(mod 5):先求3模5的逆,是2,所以解得 t ≡ 2 ( m o d   5 ) t\equiv 2(mod\ 5) t2(mod 5),转换为等式 t = 2 + 5 u t = 2 + 5u t=2+5u
   3)第1个和第2个同余式合并的结果。把 t = 2 + 5 u t = 2 + 5u t=2+5u代入 x = 2 + 3 t x = 2 + 3t x=2+3t x = 8 + 15 u x = 8 + 15u x=8+15u,即 x ≡ 8 ( m o d   15 ) x\equiv 8 (mod\ 15) x8(mod 15)
   4)把 x = 8 + 15 u x = 8 + 15u x=8+15u代入第3个同余式得: 8 + 15 u ≡ 2 ( m o d   7 ) 8 + 15u \equiv 2 (mod\ 7) 8+15u2(mod 7)
   5)求解 8 + 15 u ≡ 2 ( m o d   7 ) 8 + 15u\equiv 2 (mod\ 7) 8+15u2(mod 7)
   首先变为 15 u ≡ − 6 ( m o d   7 ) 15u\equiv -6 (mod\ 7) 15u6(mod 7) g c d ( 15 , 7 ) gcd(15, 7) gcd(15,7)能整除 − 6 -6 6,有解。
   然后求 15 u ≡ − 6 ( m o d   7 ) 15u\equiv -6 (mod\ 7) 15u6(mod 7):先求15模7的逆,是1,解得 u ≡ 1 ( m o d   7 ) u\equiv 1(mod\ 7) u1(mod 7),转换为 u = 1 + 7 v u = 1 + 7v u=1+7v
   6)得到合并结果。把 u = 1 + 7 v u = 1 + 7v u=1+7v代入 x = 8 + 15 u x = 8 + 15u x=8+15u,得 x = 23 + 105 v x = 23 + 105v x=23+105v,即 x ≡ 23 ( m o d   105 ) x\equiv 23(mod\ 105) x23(mod 105)。结束。

2、 编码步骤
  下面改用丢番图方程的形式,总结合并两个同余式的编码方法,并以合并上面的前2个等式为例。

步骤 例子
合并两个等式:
x = a 1 + X m 1 x = a_1 + Xm_1 x=a1+Xm1
x = a 2 + Y m 2 x = a_2 + Ym_2 x=a2+Ym2
x = 2 + 3 X x = 2 + 3X x=2+3X
x = 3 + 5 Y x = 3 + 5Y x=3+5Y
两个等式相等: a 1 + X m 1 = a 2 + Y m 2 a_1 + Xm_1 =a_2 + Ym_2 a1+Xm1=a2+Ym2
移项得: X m 1 + ( − Y ) m 2 = a 2 − a 1 Xm_1 + (-Y)m_2 = a_2 - a_1 Xm1+(Y)m2=a2a1
2 + 3 X = 3 + 5 Y 2 + 3X = 3 + 5Y 2+3X=3+5Y
3 X + 5 ( − Y ) = 1 3X + 5(-Y) = 1 3X+5(Y)=1
这是形如 a X + b Y = c aX+bY = c aX+bY=c的丢番图方程。
下面求解它。先用扩展欧几里得求 X 0 X_0 X0
得: X 0 = 2 X_0 =2 X0=2
X X X的通解是 X = X 0 c / d + ( b / d ) n X = X_0 c / d + (b/d)n X=X0c/d+(b/d)n
最小值是 t = ( X 0 c / d ) m o d   ( b / d ) t = (X_0 c / d) mod\ (b/d) t=(X0c/d)mod (b/d)
t = ( X 0 c / d ) m o d   ( b / d ) t = (X_0 c / d) mod\ (b/d) t=(X0c/d)mod (b/d)
= ( 2 × 1 / 1 ) m o d   ( 5 / 1 ) = 2 = (2×1/1) mod\ (5/1) = 2 =(2×1/1)mod (5/1)=2
X = t X = t X=t代入 x = a 1 + X m 1 x = a_1 + Xm_1 x=a1+Xm1
求得原等式的一个特解 x ′ x' x
x ′ = 2 + 2 × 3 = 8 x' = 2 + 2×3 = 8 x=2+2×3=8
合并后的新 x = a + X m x = a + Xm x=a+Xm
m = m 1 m 2 / g c d ( m 1 , m 2 ) m = m_1m_2/gcd(m_1, m_2) m=m1m2/gcd(m1,m2)
a = x ′ a = x' a=x
m = 3 × 5 / 1 = 15 m = 3×5/1 = 15 m=3×5/1=15
a = 8 a = 8 a=8
合并后的新方程是 x = 8 + 15 X x = 8 + 15X x=8+15X
x ≡ 8 ( m o d 15 ) x\equiv 8 (mod 15) x8(mod15)

3、 例程
  下面用一个模板题给出线性同余方程组的代码。


扩展中国剩余定理 洛谷P4777
题目描述:给定n组非负整数 a i , m i a_i,m_i aimi,求解关于 x x x的方程组的最小非负整数解。1≤n≤10^5,1 ≤ ai ≤ 10^12, 1 ≤ m i < a i 1 ≤ m_i < a_i 1mi<ai,保证所有 a i a_i ai的最小公倍数不超过 10^18。
     x ≡ a 1 ( m o d   m 1 ) x\equiv a_1 (mod\ m_1) xa1(mod m1)
     x ≡ a 2 ( m o d   m 2 ) x\equiv a_2 (mod\ m_2) xa2(mod m2)
    …
     x ≡ a n ( m o d   m n ) x\equiv a_n (mod\ m_n) xan(mod mn)
输入:第一行是整数n,后面n行,每行两个非负整数 a i , m i a_i,m_i aimi
输出:输出一行,为满足条件的非负整数x。


  下面给出的代码2,和前面“编码步骤”基本一致。
  注意代码中的细节,例如求 t = ( X 0 c / d ) m o d   ( b / d ) t = (X_0 c / d) mod\ (b/d) t=(X0c/d)mod (b/d)的代码是 m u l ( x , c / d , b / d ) mul(x, c/d, b/d) mul(x,c/d,b/d),目的是避免越界。其他细节见注释。

#include
using namespace std;
typedef long long ll;
const int maxn = 100010;
int n;
ll ai[maxn], mi[maxn];
ll mul(ll a,ll b,ll mod){   //乘法取模:a*b % mod
    ll res=0;
    while(b>0){
        if(b&1) res=(res+a)%mod;
        a=(a+a)%mod;
        b>>=1;
    }
    return res;
}
ll extend_gcd(ll a,ll b,ll &x,ll &y){   //扩展欧几里得
    if(b == 0){ x=1; y=0; return a;}
    ll d = extend_gcd(b,a%b,y,x);
    y -= a/b * x;
    return d;
}
ll excrt(){               //求解同余方程组,返回最小正整数解
    ll x,y;
    ll m1 = mi[1], a1 = ai[1];      //第1个等式
    ll ans = 0;
    for(int i=2;i<=n;i++){          //合并每2个等式       
        ll a2 = ai[i], m2 = mi[i];  // 第2个等式 
        //合并为:aX + bY = c      
        ll a = m1, b = m2, c = (a2 - a1%m2 + m2) % m2;
        //下面求解 aX + bY = c
        ll d = extend_gcd(a,b,x,y);  //用扩展欧几里得求x0
        if(c%d != 0) return -1;      //无解
        x = mul(x,c/d,b/d);          //aX + bY = c 的特解t,最小值         
        
        ans = a1 + x* m1;            //代回原第1个等式,求得特解x'
        m1 = m2/d*m1;                //先除再乘,避免越界。合并后的新m1
        ans = (ans%m1 + m1) % m1;    //最小正整数解
        a1 = ans;                    //合并后的新a1
    }
    return ans;
}

int main(){
    scanf("%d", &n);
    for(int i=1;i<=n;++i)  
        scanf("%lld%lld",&mi[i],&ai[i]);
    printf("%lld",excrt());
    return 0;
}

  1. 公元3世纪《孙子算经》中有一个问题:“今有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二,问物几何?答曰:二十三。”1247年,秦九韶在《数学九章》中给出了求解的一般方法“大衍求一术”,被称为“中国剩余定理(Chinese Remainder Theorem)”。秦九韶是全能型的天才,在多个领域有建树。 ↩︎

  2. 参考:https://www.luogu.com.cn/problem/solution/P4777 ↩︎

你可能感兴趣的:(同余 --算法竞赛专题解析(22):数论)