夜深人静写算法(三)- 初等数论入门

一、前言

  最优秀的人,往往是最努力的,如果没有了勤奋努力的学习,天才也将一无所获。所以,我从来没有停止过学习,只要你不抗拒学习,无论从广度还是深度上,都秉持着一种钻研的态度,遇到问题钻研到底,那么我相信,再难的问题都会有解决的一天。
  高中那时候最流行的一句话叫:学好数理化,走遍天下都不怕!确实,数学对我而言,也是一个又爱又恨的存在,本来引以为傲的数学,高考的时候没有发挥好,最后和自己想去的大学失之交臂。正所谓,成也数学,败也数学。不过,这已经不重要了,把握当下吧!学习算法的途中,邂逅了数论,让我对数学又重燃激情,那么这一章,就让我带领大家来见识一下数论的魅力吧!
在这里插入图片描述

二、数论基本概念

1、整除性

  • a a a b b b 都为整数, a a a 整除 b b b 是指 b b b a a a 的倍数, a a a b b b 的约数(因数、因子),记为 a ∣ b a|b ab。整除的大部分性质都是显而易见的,为了阐述方便,我给这些性质都起了个名字。
  • i) 任意性:若 a ∣ b a|b ab,则对于任意非零整数 m m m,有 a m ∣ b m am|bm ambm
  • ii) 传递性:若 a ∣ b a|b ab b ∣ c b|c bc ,则 a ∣ c a|c ac
  • iii) 可消性:若 a ∣ b c a|bc abc a a a c c c 互素(互素的概念下文会讲到),则 a ∣ b a|b ab
  • iv) 组合性:若 c ∣ a c|a ca c ∣ b c|b cb,则对于任意整数 m 、 n m、n mn,有 c ∣ ( m a + n b ) c|(ma+nb) c(ma+nb)
  • 拿一个我还未出生时的初二数学竞赛题就能概括整除的性质了。

【例题1】(公元1987年初二数学竞赛题) x , y , z x,y,z xyz 均为整数,若 11 ∣ ( 7 x + 2 y − 5 z ) 11|(7x+2y-5z) 11(7x+2y5z),求证: 11 ∣ ( 3 x − 7 y + 12 z ) 11|(3x-7y+12z) 11(3x7y+12z)

  • 为了描述方便,令: a = ( 7 x + 2 y − 5 z ) , b = ( 3 x − 7 y + 12 z ) a = (7x+2y-5z), b = (3x-7y+12z) a=(7x+2y5z),b=(3x7y+12z)
  • 通过构造,可以得到一个等式: 3 a + 4 b = 11 ∗ ( 3 x − 2 y + 3 z ) 3a + 4b = 11*(3x-2y+3z) 3a+4b=11(3x2y+3z)
  • 根据任意性+组合性,得出: 11 ∣ ( 11 ∗ ( 3 x − 2 y + 3 z ) − 3 a ) = 11 ∣ 4 b 11|(11*(3x-2y+3z) - 3a) = 11|4b 11(11(3x2y+3z)3a)=114b
  • 然后根据可消性,由于 11 11 11 4 4 4 互素,得出 11 ∣ b 11|b 11b,证明完毕。

2、素数

1)素数与合数

  • 素数又称质数,素数首先满足条件是要大于等于 2,并且除了 1 和它本身外,不能被其它任何自然数整除;
  • 其它的数称为合数;
  • 而 1 既非素数也非合数;
  • 特殊的,2 是唯一的偶素数。

2)素数判定

  • 如何判定一个数是否为素数?

朴素算法

  • n n n [ 2 , n ) [2, n) [2,n) 范围内的余数判定(c/c++中 的 '%'运算符),如果有至少一个数用 n n n 取余后为 0,则表明 n n n 为合数;如果所有数都不能整除 n n n,则 n n n 为素数,算法复杂度 O ( n ) O(n) O(n)

优化1

  • 假设一个数 a a a 能整除 n n n,即 a ∣ n a|n an,那么 n a \frac n a an 也必定能整除 n n n,不妨设 a < = n a a <= \frac {n}{a} a<=an,则有 a 2 < = n a^2 <= n a2<=n,即 a < = n a <= \sqrt{n} a<=n ,所以在用朴素算法进行取余的时候,范围可以缩小到 n \sqrt{n} n ,所以算法复杂度降为 O ( n ) O( \sqrt{n} ) O(n )

优化2

  • 如果 n n n 是合数,那么它必然有一个小于等于 n \sqrt n n 的素因子,只需要对 n \sqrt n n 内的素数进行测试即可,需要预处理求出 n \sqrt n n 中的素数,假设该范围内素数的个数为 s s s,那么复杂度降为 O ( s ) O(s) O(s)

3)素数定理

  • x x x 很大时,小于 x x x 的素数的个数近似等于 x / l n ( x ) x / ln(x) x/ln(x),其中 l n ( x ) ln(x) ln(x) 表示 x x x 的自然对数,用极限表示如下:

lim ⁡ x → ∞ π ( x ) x / l n ( x ) = 1 \lim\limits_{x\rightarrow\infty}\frac{π(x)}{x / ln(x)}=1 xlimx/ln(x)π(x)=1

  • 从这个定理可以发现,程序中进行素数判定的时候,优化2优化1 差了至少一个数量级。

4)素数筛选法

【例题2】给定 n ( n < 10000 ) n(n < 10000) n(n<10000) 个数,范围为 [ 1 , 2 30 ) [1, 2^{30}) [1,230),判定它是素数还是合数。

  • 首先 1 不是素数,如果 n > 1 n>1 n>1,则枚举 [ 1 , n ] [1,\sqrt n] [1n ] 范围内的素数进行试除,如果至少有一个素数能够整除 n n n,则表明 n n n 是合数,否则是素数。
  • [ 1 , n ] [1,\sqrt n] [1n ] 范围内的素数可以通过筛选法预先筛出来,用一个数组 notprime[i]标记 i i i 是素数与否,筛选法有很多,这里介绍一种最常用的筛选法——Eratosthenes筛选法。
  • C++代码实现如下:
#define maxp 65536
#define ll long long

int primes[maxp];
bool notprime[maxp];                                                        // 1
 
void Eratosthenes() {
    
    
    notprime[1] = true;                                                    
    primes[0] = 0;
    for (int i = 2; i < maxp; i++) {
    
    
        if (!notprime[i]) {
    
    
            primes[++primes[0]] = i;                                       // 2 
            
            for (ll j = (ll)i*i; j < maxp; j += i) {
    
    
                notprime[j] = true;                                        // 3
            }
        }
    }
}
  • 1)notprime[i] == true表明 i i i 不是素数,否则 i i i 为素数(因为全局变量初始值为 false,筛选法预处理只做一次,所以不需要初始化);

  • 2)首先从小到大进行枚举,遇到notprime[i] == false的,表明 i i i 是素数,将 i i i 保存到数组primes[]中(为了少写一个变量,用primes[0]来存储素数个数字段);

  • 3)将 i i i 的倍数都标记为合数,由于 i ∗ 2 、 i ∗ 3 、 i ∗ ( i − 1 ) i*2、i*3、i*(i-1) i2i3i(i1) [ 1 , i ) [1, i) [1,i) 的筛选过程中必定已经被标记为合数了,所以 i i i 的倍数只需要从 i ∗ i i*i ii 开始即可,避免不必要的时间开销。需要注意 i ∗ i i*i ii 超出整型后变成负数的问题,所以转化成 long long

  • 分析:虽然这个算法有两个嵌套的轮询,但是第二个轮询只有在 i i i 是素数的时候才会执行,而且随着 i i i 的增大,它的倍数会越来越少,所以整个算法的时间复杂度并不是 O ( n 2 ) O(n^2) O(n2),而且远远小于 O ( n 2 ) O(n^2) O(n2)

  • ( 可以在内层循环notprime进行赋值前加入一个计数器 c o u n t count count,计数器的值就是该程序的总执行次数,对 m a x p maxp maxp 进行不同的值测试发现 i n t ( c o u n t / m a x p ) int(count / maxp) int(count/maxp) 的值随着 m a x p maxp maxp 的增长变化非常小,总是维持在 2 左右,所以这个算法的复杂度可以近似看成 是 O ( n ) O(n) O(n),更加确切的可以说是 O ( n C ) O(nC) O(nC),其中 C C C 为常数, C C C 一般取 2 )。

  • 事实上,实际应用中由于空间的限制(空间复杂度为 O ( n ) O(n) O(n)), m a x n maxn maxn 的值并不会取的很大, 1 0 7 10^7 107 基本已经算是极限了,再大的素数测试就需要用到 拉宾-米勒 ( R a b i n − M i l l e r Rabin-Miller RabinMiller) 大数判素了,这个会在后面章节进行展开。

3、因数分解

1)算术基本定理

  • 算术基本定理可以描述为:对于每个整数 n n n,都可以唯一分解成素数的乘积,如下:
    n = p 1 p 2 p 3 . . . p k n = p_1p_2p_3...p_k n=p1p2p3...pk ( p 1 < = p 2 < = p 3 < = . . . < = p k ) (p_1 <= p_2 <= p_3 <= ... <= p_k) (p1<=p2<=p3<=...<=pk)
  • 这里的素数并不要求是不一样的,所以可以将相同的素数进行合并,采用素数幂的乘积进行表示,如下:
    n = p 1 e 1 p 2 e 2 p 3 e 3 . . . p k e k n = p_1^{e_1}p_2^{e_2}p_3^{e_3}...p_k^{e_k} n=p1e1p2e2p3e3...pkek ( p 1 < p 2 < p 3 < . . . < p k 且 e i > 0 ) (p_1 < p_2 < p_3 < ... < p_k 且 e_i > 0) (p1<p2<p3<...<pkei>0)

2)素数拆分

  • 给定一个数 n n n,如何将它拆分成素数的乘积呢?

  • 还是用上面讲到的试除法,假设 n = p m n = pm n=pm 并且 m > 1 m>1 m>1,其中 p p p 为素数,如果 p > n p > \sqrt n p>n ,那么根据算数基本定理, m m m 中必定存在一个小于等于 n \sqrt n n 的素数,所以我们不妨设 p < = n p <= \sqrt n p<=n

  • 然后通过枚举 [ 2 , n ] [2, \sqrt n] [2,n ] 的素数,如果能够找到一个素数 p p p,使得 n m o d    p ≡ 0 n \mod p \equiv 0 nmodp0(mod 表示取余数、也称为模)。于是 m = n / p m = n / p m=n/p,这时还需要注意一点,因为 m m m 中可能也有 p p p 这个素因子,所以如果 p ∣ m p|m pm,需要继续试除,令 m ′ = m / p m' = m/p m=m/p,直到将所有的素因子 p p p 除尽,统计除的次数 e e e,于是我们得到了 n = p e ∗ n ′ n = p^e * n' n=pen,然后继续枚举素数对 n ′ n' n 做同样的试除。

  • 枚举完 [ 2 , n ] [2, \sqrt n] [2,n ] 的素数后,得到表达式如下所示:
    n = p 1 e 1 p 2 e 2 p 3 e 3 . . . p k e k S n = p_1^{e_1}p_2^{e_2}p_3^{e_3}...p_k^{e_k} S n=p1e1p2e2p3e3...pkekS

  • 这时有两种情况:

  • i) S = = 1 S == 1 S==1,则素数分解完毕;

  • ii) S > 1 S > 1 S>1, 根据算术基本定理, S S S 必定为素数,而且是大于 n \sqrt n n 的素数,并且最多只有 1 个,这种情况同样适用于 n n n 本身就是素数的情况,这时 n = S n = S n=S

  • 这样的分解方式称为因数分解,各个素因子可以用一个二元的结构体来存储。算法时间复杂度为 O ( s ) O(s) O(s) s s s n \sqrt n n 内素数的个数。

  • C++ 代码实现如下:

struct factor {
    
    
    int prime, count;
    factor() {
    
    }
    factor(int p, int c) : prime(p), count(c) {
    
    }
};

void Factorization(int n, vector <factor>& ans) {
    
    
    ans.clear();
    if (n == 1) {
    
    
        return;
    }
    for (int i = 1; i <= primes[0]; i++) {
    
    
        if (0 == n % primes[i]) {
    
    
            factor f(primes[i], 0);
            while (0 == n % primes[i]) {
    
    
                n /= primes[i];
                f.count++;
            }
            ans.push_back(f);
        }
        if (n == 1) {
    
    
            return;
        }
    }
    ans.push_back(factor(n, 1));
}

3)因子个数

  • 朴素的求因子个数的方法为枚举 [ 1 , n ] [1, n] [1,n] 的数进行余数为 0 判定,复杂度为 O ( n ) O(n) O(n),这里加入一个小优化,如果 m m m n n n 的因子,那么必然 n / m n/m n/m 也为 n n n 的因子,不妨设 m < = n / m m <= n/m m<=n/m,则有 m < = n m <= \sqrt n m<=n ,所以只要枚举从 [ 1 , n ] [1, \sqrt n] [1,n ] 的因子然后计数即可,复杂度变为 O ( n ) O(\sqrt n) O(n )

  • 根据算术基本定理,容易发现 n n n 的因子一定是 p 1 、 p 2 、 . . . 、 p k p_1、p_2、...、p_k p1p2...pk 的组合,并且 p 1 p_1 p1 可以取的个数为 [ 0 , e 1 ] [0, e_1] [0,e1] p 2 p_2 p2 可以取的个数为 [ 0 , e 2 ] [0, e_2] [0,e2] p k p_k pk 可以取的个数为 [ 0 , e k ] [0, e_k] [0,ek],所以根据乘法原理,总的因子个数记为 g ( n ) g(n) g(n),等于 e i + 1 e_i+1 ei+1 的连乘,即: g ( n ) = ∏ i = 1 k ( e i + 1 ) g(n) = \prod_{i=1}^{k} (e_i + 1) g(n)=i=1k(ei+1)

  • 算法时间复杂度即求素因子分解的复杂度,为 O ( s ) O(s) O(s)

【例题3】给定 x , y ( x , y < 2 31 ) x,y(x, y < 2^{31}) x,y(x,y<231),求 x y x^y xy 的因子数 m o d    10007 \mod 10007 mod10007

  • 由于这里的 x y x^y xy 已经是天文数字,利用上述的枚举法已经无法满足要求,所以我们需要换个思路。考虑到任何整数都能表示成素数的乘积,那么 x y x^y xy 也不例外,我们首先将 x x x 进行因数分解,那么 x y x^y xy 可以表示成如下形式:
    x y = ( p 1 e 1 p 2 e 2 p 3 e 3 . . . p k e k ) y = p 1 y e 1 p 2 y e 2 p 3 y e 3 . . . p k y e k \begin{aligned} x^y &= (p_1^{e_1}p_2^{e_2}p_3^{e_3}...p_k^{e_k}) ^ y \\ &= p_1^{ye_1}p_2^{ye_2}p_3^{ye_3}...p_k^{ye_k} \end{aligned} xy=(p1e1p2e2p3e3...pkek)y=p1ye1p2ye2p3ye3...pkyek
  • 直接套用因子数公式,得到: g ( x y ) m o d    10007 = ∏ i = 1 k ( y e i + 1 ) m o d    10007 g(x^y) \mod 10007= \prod_{i=1}^{k} (ye_i + 1) \mod 10007 g(xy)mod10007=i=1k(yei+1)mod10007

4)因子和

  • 考虑数 n n n,令 n n n 的因子和为 s ( n ) s(n) s(n),对 n n n 进行素数分解后的,假设最小素数为 p p p,素因子 p p p 的个数为 e e e ,那么 n = p e n ′ n = p^en' n=pen
  • 容易得知,当 n n n 的因子中 p p p 的个数为 0 0 0 时,因子之和为 s ( n ′ ) s(n') s(n)。更加一般地,当 n n n 的因子中 p p p 的个数为 k k k 的时候,因子之和为 p k ∗ s ( n ′ ) p^k * s(n') pks(n),所以 n n n 的所有因子之和就可以表示成:
    s ( n ) = ( p 0 + p 1 + p 2 + . . . p e ) ∗ s ( n ′ ) = p e + 1 − 1 p − 1 ∗ s ( n ′ ) \begin {aligned} s(n) &= (p^0 + p^1 + p^2 + ... p^e) * s(n') \\ &= \frac {p^{e+1} - 1} {p-1} * s(n') \end {aligned} s(n)=(p0+p1+p2+...pe)s(n)=p1pe+11s(n)
  • s(n’)可以通过相同方法递归计算。最后可以表示成一系列等比数列和的乘积。如下:
    s ( n ) = ∏ i = 1 k p i e i + 1 − 1 p i − 1 s(n) =\prod_{i=1}^k \frac {p_i^{e_i+1} - 1} {p_i-1} s(n)=i=1kpi1piei+11

【例题4】给定 x , y ( x , y < 2 31 ) x,y(x, y < 2^{31}) x,y(x,y<231),求 x y x^y xy 的所有因子之和 m o d    10007 \mod 10007 mod10007

  • 参考 【例题3】的做法求解即可。

4、最大公约数 (GCD) 和 最小公倍数 (LCM)

  • 两个数 a a a b b b 的最大公约数 (Greatest Common Divisor) 是指同时整除 a a a b b b 的最大因数,记为 g c d ( a , b ) gcd(a, b) gcd(a,b)。特殊的,当 g c d ( a , b ) = 1 gcd(a, b) = 1 gcd(a,b)=1,我们称 a a a b b b 互素(上文谈到整除的时候略有提及)。

  • 两个数 a a a b b b 的最小公倍数 (Leatest Common Multiple) 是指同时被 a a a b b b 整除的最小倍数,记为 l c m ( a , b ) lcm(a, b) lcm(a,b)。特殊的,当 a a a b b b 互素时, l c m ( a , b ) = a b lcm(a, b) = ab lcm(a,b)=ab

  • g c d gcd gcd 是基础数论中非常重要的概念,求解 g c d gcd gcd 一般采用辗转相除法(这个方法会在第二章开头着重介绍,这里先引出概念),而求 l c m lcm lcm 需要先求 g c d gcd gcd,然后容易得到: l c m ( a , b ) = a b g c d ( a , b ) lcm(a, b) = \frac {ab} { gcd(a, b)} lcm(a,b)=gcd(a,b)ab

  • 根据算术基本定理,有如下公式满足:
    a = p 1 x 1 p 2 x 2 p 3 x 3 . . . p k x k a = p_1^{x_1}p_2^{x_2}p_3^{x_3}...p_k^{x_k} a=p1x1p2x2p3x3...pkxk b = p 1 y 1 p 2 y 2 p 3 y 3 . . . p k y k b = p_1^{y_1}p_2^{y_2}p_3^{y_3}...p_k^{y_k} b=p1y1p2y2p3y3...pkyk

  • 那么 g c d ( a , b ) gcd(a,b) gcd(a,b) l c m ( a , b ) lcm(a,b) lcm(a,b) 可以表示成如下等式:
    g c d ( a , b ) = p 1 m i n ( x 1 , y 1 ) p 2 m i n ( x 2 , y 2 ) p 3 m i n ( x 3 , y 3 ) . . . p k m i n ( x k , y k ) gcd(a,b)=p_1^{min(x_1,y_1)}p_2^{min(x_2,y_2)}p_3^{min(x_3,y_3)}...p_k^{min(x_k,y_k)} gcd(a,b)=p1min(x1,y1)p2min(x2,y2)p3min(x3,y3)...pkmin(xk,yk) l c m ( a , b ) = p 1 m a x ( x 1 , y 1 ) p 2 m a x ( x 2 , y 2 ) p 3 m a x ( x 3 , y 3 ) . . . p k m a x ( x k , y k ) lcm(a,b)=p_1^{max(x_1,y_1)}p_2^{max(x_2,y_2)}p_3^{max(x_3,y_3)}...p_k^{max(x_k,y_k)} lcm(a,b)=p1max(x1,y1)p2max(x2,y2)p3max(x3,y3)...pkmax(xk,yk)

  • 需要说明的是这里的 a a a b b b 的分解式中的指数是可以为 0 的,也就是说 p 1 p_1 p1 a a a b b b 中某一个数的最小素因子, p 2 p_2 p2 是次小的素因子。 l c m ( a , b ) lcm(a, b) lcm(a,b) g c d ( a , b ) gcd(a, b) gcd(a,b) 相乘,相当于等式右边的每个素因子的指数相加,即 m i n ( x i , y i ) + m a x ( x i , y i ) = x i + y i min(x_i, y_i) + max(x_i, y_i) = x_i + y_i min(xi,yi)+max(xi,yi)=xi+yi,正好对应了 a a a b b b 的第 i i i 个素数分量的指数之和,从而得到:
    a b = l c m ( a , b ) ∗ g c d ( a , b ) ab = lcm(a, b) * gcd(a, b) ab=lcm(a,b)gcd(a,b)

【例题5】三个未知数 x , y , z x, y, z x,y,z ,且 g c d ( x , y , z ) = G , l c m ( x , y , z ) = L gcd(x,y,z) = G,lcm(x,y,z) = L gcd(x,y,z)=Glcm(x,y,z)=L G G G L L L 已知,求 ( x , y , z ) (x, y, z) (x,y,z) 三元组的个数。

  • 三个数的gcd可以参照两个数gcd的指数最值表示法,只不过每个素因子的指数上是三个数的最值(即 m i n ( x i , y i , z i ) min(x_i, y_i, z_i) min(xi,yi,zi) ),那么这个问题首先要做的就是将 G G G L L L 分别进行素因子分解,然后轮询L的每个素因子,对于每个素因子单独处理。
  • 假设素因子为 p p p L L L 分解式中 p p p 的指数为 l l l G G G 分解式中 p p p 的指数为 g g g ,那么显然 l < g l < g l<g 时不可能存在满足条件的三元组,所以只需要讨论 l > = g l >= g l>=g 的情况,对于单个 p p p 因子,问题转化成了求三个数 x i , y i , z i x_i, y_i, z_i xi,yi,zi,满足 m i n ( x i , y i , z i ) = g min(x_i, y_i, z_i) = g min(xi,yi,zi)=g m a x ( x i , y i , z i ) = l max(x_i, y_i, z_i) = l max(xi,yi,zi)=l,更加通俗的意思就是三个数中最小的数是 g g g ,最大的数是 l l l ,另一个数在 [ g , l ] [g, l] [g,l] 范围内,这是一个排列组合问题,三元组 ( x i , y i , z i ) (x_i, y_i, z_i) (xi,yi,zi) 的种类数当 l = = g l == g l==g 时只有 1 种,否则答案就是 6 ( l − g ) 6(l - g) 6(lg),具体如下表所示:
( x i , y i , z i ) (x_i,y_i,z_i) (xi,yi,zi) 排列后种数
(g,l,l) 3
(g,g,l) 3
(g,k,l),其中 (g<k<l) (l-g-1)*6
  • 最后根据乘法原理将每个素因子对应的种类数相乘就是最后的答案了。

5、同余

1)模运算

  • 给定一个正整数 p p p,任意一个整数 n n n,一定存在等式 n = k p + r n = kp + r n=kp+r; 其中 k 、 r k、r kr 是整数,且满足 0 < = r < p 0 <= r < p 0<=r<p,称 k k k n n n 除以 p p p 的商, r r r n n n 除以 p p p 的余数,表示成 n n % p = r n (这里采用C++语法,% 表示取模运算)。
  • 对于正整数和整数 a a a, b b b, 定义如下运算:
  • i)取模运算: a % p a \% p a%p ( a m o d    p ) (a \mod p) (amodp),表示 a a a 除以 p p p 的余数;
  • ii)模 p p p 加法: ( a + b ) % p = ( a % p + b % p ) % p (a + b) \% p = (a\%p + b\%p) \% p (a+b)%p=(a%p+b%p)%p
  • iii)模 p p p 减法: ( a − b ) % p = ( a % p − b % p ) % p (a - b) \% p = (a\%p - b\%p) \% p (ab)%p=(a%pb%p)%p
  • iv)模 p p p 乘法: ( a ∗ b ) % p = ( ( a % p ) ∗ ( b % p ) ) % p (a * b) \% p = ((a \% p)*(b \% p)) \% p (ab)%p=((a%p)(b%p))%p
  • v)幂模 p p p ( a b ) % p = ( ( a % p ) b ) % p (a^b) \% p = ((a \% p)^b) \% p (ab)%p=((a%p)b)%p
  • 模运算满足结合律、交换律和分配律。 a ≡ b ( m o d    n ) a \equiv b (\mod n) ab(modn) 表示 a a a b b b n n n 同余,即 a a a b b b 除以 n n n 的余数相等。

【例题6】一个 n n n 位十进制数 ( n < = 1000000 ) (n <= 1000000) (n<=1000000) 必定包含 1 、 2 、 3 、 4 1、2、3、4 1234 四个数字,现在将它顺序重排,求给出一种方案,使得重排后的数是 7 7 7 的倍数。

  • 取出1、2、3、4后,将剩下的数字随便排列得到一个数a,令四个数字排列出来的数为b,那么就是要找到一种方案使得 ( a ∗ 10000 + b ) % 7 = 0 (a*10000 + b) \% 7 = 0 (a10000+b)%7=0
  • 但是 a a a 真的可以随便排吗?也就是说如果无论a等于多少,都能找到这样的b满足等式成立,那么a就可以随便排。我们将等式简化: ( a ∗ 10000 + b ) % 7 = ( a ∗ 10000 % 7 + b % 7 ) % 7 (a*10000 + b) \% 7 = (a*10000\%7 + b\%7) \% 7 (a10000+b)%7=(a10000%7+b%7)%7
  • k = a ∗ 10000 % 7 = a ∗ 4 % 7 k = a*10000\%7 = a*4\%7 k=a10000%7=a4%7,容易发现 k k k 的取值为 [ 0 , 7 ) [0, 7) [0,7),如果 b % 7 b\%7 b%7 的取值也是 [ 0 , 7 ) [0, 7) [0,7),那这个问题就可以完美解决了,很幸运的是,的确可以构造出 7 个这样的 b b b。具体参见如下表格:
b%7 0 1 2 3 4 5 6
b 4312 2143 1234 4231 1243 2413 4213

2)快速幂取模

  • 幂取模常常用在加密算法中运用较多,是指给定整数 a a a,正整数 n n n,以及非零整数 p p p,求 a n % p a^n \% p an%p。利用模 p p p 乘法,这个问题可以递归求解,即令:
    f ( n ) = a n % p f(n) = a^n\%p f(n)=an%p
  • 则可以得到递推式如下:
    f ( n ) = a ∗ f ( n − 1 ) % p f(n) = a*f(n-1) \% p f(n)=af(n1)%p
  • 这样就转化成了递归的形式。但是递归求解的时间复杂度为 O ( n ) O(n) O(n),往往当 n n n 很大的时候就很难在规定时间内出解了。
  • 关于幂取模的问题,可以采用二分求解,当指数非常大的时候还可以采用扩展欧拉定理进行降幂操作,具体实现方式会在后面的连载中进行详细分析,本节先给出公式:
    a b m o d    c = { 1 m o d    c b 为 0 a ( a ( b − 1 ) / 2 ) 2 m o d    c b 为 奇 数 ( a b / 2 ) 2 m o d    c b 为 正 偶 数 a^b \mod c = \begin {cases} 1 \mod c & b 为 0 \\ a(a^{(b-1)/2})^2 \mod c & b为奇数\\ (a^{b/2})^2 \mod c & b为正偶数\\ \end{cases} abmodc=1modca(a(b1)/2)2modc(ab/2)2modcb0bb

3)循环节

【例题7】 f [ 1 ] = a , f [ 2 ] = b , f [ 3 ] = c f[1] = a, f[2] = b, f[3] = c f[1]=a,f[2]=b,f[3]=c, 当 n > 3 n>3 n>3 f [ n ] = ( A ∗ f [ n − 1 ] + B ∗ f [ n − 2 ] + C ∗ f [ n − 3 ] ) % 54 f[n] = (A*f[n-1] + B*f[n-2] + C*f[n-3]) \% 54 f[n]=(Af[n1]+Bf[n2]+Cf[n3])%54,给定 a , b , c , A , B , C a, b, c, A, B, C a,b,c,A,B,C,求 f [ n ] f[n] f[n],(其中 n < 2 31 n < 2^{31} n<231)。

  • 由于 n n n 非常大,迭代模拟求解肯定是不现实的,仔细观察可以发现当 n > 3 n>3 n>3 时, f [ n ] f[n] f[n] 的值域为 [ 0 , 53 ) [0, 53) [0,53),并且连续三个数 f [ n − 1 ] 、 f [ n − 2 ] 、 f [ n − 3 ] f[n-1]、f[n-2]、f[n-3] f[n1]f[n2]f[n3] 一旦确定,那么 f [ n ] f[n] f[n] 也就确定了,而 f [ n − 1 ] 、 f [ n − 2 ] 、 f [ n − 3 ] f[n-1]、f[n-2]、f[n-3] f[n1]f[n2]f[n3] 这三个数的组合数为 54 ∗ 54 ∗ 54 54*54*54 545454 种情况;
  • 那么对于一个下标 k < n k < n k<n,假设 f [ k ] f[k] f[k] 已经求出,并且满足 f [ k − 1 ] = = f [ n − 1 ] f[k-1] == f[n-1] f[k1]==f[n1] f [ k − 2 ] = = f [ n − 2 ] f[k-2] == f[n-2] f[k2]==f[n2] f [ k − 3 ] = = f [ n − 3 ] f[k-3] == f[n-3] f[k3]==f[n3], 则 f [ n ] f[n] f[n] 必定等于 f [ k ] f[k] f[k],这里的 f [ k . . . n − 1 ] f[k...n-1] f[k...n1] 就被称为这个数列的循环节。
  • 并且在 54 ∗ 54 ∗ 54 54*54*54 545454 次计算之内必定能够找到循环节,这个是显而易见的。
  • 当然,某些模数较小的 幂取模 的问题,也可以采用循环节的方式求解。

二、数论基础知识

1、欧几里德定理(辗转相除)

定理: g c d ( a , b ) = g c d ( b , a % b ) gcd(a, b) = gcd(b, a \% b) gcd(a,b)=gcd(b,a%b)

  • 证明: a = k b + r = k b + a % b a = kb + r = kb + a \%b a=kb+r=kb+a%b,则 a % b = a − k b a \% b = a - kb a%b=akb。令 d d d a a a b b b 的公约数,则 d ∣ a d|a da d ∣ b d|b db 根据整除的组合性原则,有 d ∣ ( a − k b ) d|(a-kb) d(akb),即 d ∣ ( a % b ) d|(a\%b) d(a%b)
  • 这就说明如果 d d d a a a b b b 的公约数,那么 d d d 也一定是 b b b a % b a\%b a%b 的公约数,即两者的公约数是一样的,所以最大公约数也必定相等。
  • 这个定理可以直接用递归实现,C++ 代码如下:
Type gcd(Type a, Type b) {
    
    
    return b ? gcd(b, a%b) : a;
}
  • 这个函数揭示了一个约定俗成的概念,即任何非零整数和零的最大公约数为它本身。

【例题8】 f [ 0 ] = 0 f[0] = 0 f[0]=0, 当 n > 1 n>1 n>1 时, f [ n ] = ( f [ n − 1 ] + a ) f[n] = (f[n-1]+a) % b f[n]=(f[n1]+a),给定 a a a b b b,问是否存在一个自然数 k ( 0 < = k < b ) k (0 <= k< b) k(0<=k<b) f [ n ] f[n] f[n] 永远都取不到的。

  • 永远有多远?并不是本题的范畴。
  • 但是可以发现的是这里的 f [ . . . ] f[...] f[...] 一定是有循环节的,如果在某个循环节内都无法找到那个自然数 k k k,那么必定是永远都找不到了。
  • 求出 f [ n ] f[n] f[n] 的通项公式,为 f [ n ] = a n % b f[n] = an \% b f[n]=an%b,令 a n = k b + r an = kb + r an=kb+r,那么这里的 r = f [ n ] r = f[n] r=f[n],如果 t = g c d ( a , b ) t = gcd(a, b) t=gcd(a,b) r = a n − k b = t ( a t n − b t k ) r = an-kb = t (\frac atn - \frac btk ) r=ankb=t(tantbk),则有 t ∣ r t|r tr,要满足所有的 r r r 使得 t ∣ r t|r tr, 只有当 t = 1 t = 1 t=1 的时候,于是这个问题的解也就出来了,只要求 a a a b b b g c d gcd gcd,如果 g c d ( a , b ) > 1 gcd(a, b) > 1 gcd(a,b)>1,则存在一个 k k k 使得 f [ n ] f[n] f[n] 永远都取不到。

2、扩展欧几里德定理

1)线性同余

  • 线性同余方程(也叫模线性方程)是最基本的同余方程,即 a x ≡ b ( m o d    n ) ax \equiv b (\mod n) axb(modn),其中 a 、 b 、 n a、b、n abn 都为常量, x x x 是未知数,这个方程可以进行一定的转化,得到: a x = k n + b ax = kn + b ax=kn+b, 这里的 k k k 为任意整数,于是我们可以得到更加一般的形式即: a x + b y + c = 0 ax + by + c = 0 ax+by+c=0
  • 这个方程就是二维空间中的直线方程,但是 x x x y y y 的取值为整数,所以这个方程的解是一些排列成直线的点集。

2)同余方程求解

  • 求解同余方程第一步是转化成一般式: a x + b y = c ax + by = c ax+by=c

这个方程的求解步骤如下:
  a) 首先求出 a a a b b b 的最大公约数 d = g c d ( a , b ) d = gcd(a, b) d=gcd(a,b),那么原方程可以转化成 d ( a x d + b y d ) = c d(a \frac xd + b \frac yd) = c d(adx+bdy)=c,容易知道 ( a x d + b y d ) (a \frac xd + b \frac yd) (adx+bdy) 为整数,如若 d d d 不能整除 c c c,方程必然无解,算法结束;否则进入b)。
  b) 由a)可以得知,方程有解则一定可以表示成 a x + b y = c = g c d ( a , b ) ∗ c ′ ax + by = c = gcd(a, b)*c' ax+by=c=gcd(a,b)c,那么我们先来看如何求解 d = g c d ( a , b ) = a x + b y d = gcd(a, b) = ax + by d=gcd(a,b)=ax+by,根据欧几里德定理,有:
d = g c d ( a , b ) = g c d ( b , a % b ) = b x ′ + ( a % b ) y ′ = b x ′ + [ a − b ∗ a b ] y ′ = a y ′ + b [ x ′ − a b y ′ ] \begin{aligned} d &= gcd(a, b) \\&= gcd(b, a\%b) \\&= bx' + (a\%b)y' \\&= bx' + [a-b*\frac ab]y' \\&= ay' + b[x' - \frac aby']\end{aligned} d=gcd(a,b)=gcd(b,a%b)=bx+(a%b)y=bx+[abba]y=ay+b[xbay]于是有 x = y ′ , y = x ′ − ( a b ) y ′ x = y', y = x' - (\frac ab)y' x=y,y=x(ba)y

  • 由于 g c d ( a , b ) gcd(a, b) gcd(a,b) 是一个递归的计算,所以在求解 ( x , y ) (x, y) (x,y) 时, ( x ′ , y ′ ) (x', y') (x,y) 其实已经利用递归计算出来了,递归出口为 b = = 0 b == 0 b==0 的时候(对比辗转相除,也是 b = = 0 b == 0 b==0 的时候递归结束),那么这时方程的解 x 0 = 1 , y 0 = 0 x_0 = 1, y_0 = 0 x0=1,y0=0

  • C++ 代码实现如下:

// aX + bY = 1 
Type ExpGcd(Type a, Type b, Type &X, Type &Y) {
    
    
    Type q, temp;
    if( !b ) {
    
    
        q = a; X = 1; Y = 0;
        return q;
    }else {
    
    
        q = ExpGcd(b, a % b, X, Y);
        temp = X; 
        X = Y;
        Y = temp - (a / b) * Y;
        return q;
    }
}
  • 扩展欧几里德算法欧几里德算法 的返回值一致,都是 g c d ( a , b ) gcd(a, b) gcd(a,b),传参多了两个未知数 X X X, Y Y Y,采用引用的形式进行传递,对应上文提到的 x , y x, y x,y,递归出口为 b = = 0 b == 0 b==0,这时返回值为当前的 a a a,因为 g c d ( a , 0 ) = a gcd(a, 0) = a gcd(a,0)=a ( X , Y ) (X, Y) (X,Y) 初值为 ( 1 , 0 ) (1, 0) (1,0),然后经过回溯不断计算新的 ( X , Y ) (X, Y) (X,Y),这个计算是利用了之前的 ( X , Y ) (X, Y) (X,Y) 进行迭代计算的,直到回溯到最上层算法终止。最后得到的 ( X , Y ) (X, Y) (X,Y) 就是方程 g c d ( a , b ) = a x + b y gcd(a, b) = ax + by gcd(a,b)=ax+by 的解。
  • 通过扩展欧几里德求的是 a x + b y = g c d ( a , b ) ax + by = gcd(a, b) ax+by=gcd(a,b) 的解,令解为 ( x 0 , y 0 ) (x0, y0) (x0,y0),代入原方程,得: a x 0 + b y 0 = g c d ( a , b ) ax_0 + by_0 = gcd(a, b) ax0+by0=gcd(a,b),如果要求 a x + b y = c = g c d ( a , b ) ∗ c ′ ax + by = c = gcd(a, b)*c' ax+by=c=gcd(a,b)c,可以将上式代入,得: a x + b y = c = ( a x 0 + b y 0 ) c ′ ax + by = c = (ax_0 + by_0)c' ax+by=c=(ax0+by0)c,则 x = x 0 c ′ , y = y 0 c ′ x = x_0c', y = y_0c' x=x0c,y=y0c,这里的 ( x , y ) (x, y) (x,y) 只是这个方程的其中一组解, x x x 的通解为 ( x 0 c ′ + k b / g c d ( a , b ) ∣ k 为 任 意 整 数 ) ( x_0c' + kb/gcd(a, b) | k为任意整数 ) (x0c+kb/gcd(a,b)k) y y y 的通解可以通过 x x x 通解的代入得出。

【例题9】有两只青蛙,青蛙 A 和 青蛙 B ,它们在一个首尾相接的数轴上。设青蛙 A 的出发点坐标是 x x x,青蛙 B 的出发点坐标是 y y y 。青蛙A一次能跳 m m m 米,青蛙 B 一次能跳 n n n 米,两只青蛙跳一次所花费的时间相同。数轴总长 L L L 米。要求它们至少跳了几次以后才会碰面。

  • 假设跳了 t t t 次后相遇,则可以列出方程: ( x + m t ) % L = ( y + n t ) % L (x + mt) \% L = (y + nt) \% L (x+mt)%L=(y+nt)%L,将未知数 t t t 移到等式左边,常数移到等式右边,得到模线性方程: ( m − n ) t % L = ( y − x ) % L (m-n) t \%L = (y-x) \% L (mn)t%L=(yx)%L
  • 利用扩展欧几里德定理可以求得 t t t 的通解 ( t 0 + k d ∣ k 为 任 意 整 数 ) (t_0 + kd | k为任意整数) (t0+kdk),由于这里需要求 t t t 的最小正整数,而 t 0 t_0 t0 不一定是最小的正整数,甚至有可能是负数,我们发现t的通解是关于 d d d 同余的,所以最后的解可以做如下处理: a n s = ( t 0 % d + d ) % d ans = (t_0 \% d + d) \% d ans=(t0%d+d)%d

3)逆元

  • 模逆元的最通俗含义可以效仿乘法 a ∗ x = 1 a*x = 1 ax=1,则称 x x x a a a 在乘法域上的逆(倒数);同样,如果 a x ≡ 1 ( m o d    n ) ax \equiv 1 (\mod n) ax1(modn),则称 b b b a a a n n n 的逆,简称逆元。求 a a a n n n 的逆元,就是模线性方程 a x ≡ b ( m o d    n ) ax \equiv b (\mod n) axb(modn) b = 1 b=1 b=1 的特殊形式,可以用扩展欧几里德求解。并且在 g c d ( a , n ) > 1 gcd(a, n) > 1 gcd(a,n)>1 时逆不存在。

3、中国剩余定理

  • 中国剩余定理求的是 模线性方程组 的解;
  • 即对于所有给定的常数 m i m_i mi a i a_i ai,求一个最小的 x x x 满足:
    x ≡ { m 1 ( m o d    a 1 ) m 2 ( m o d    a 2 ) . . . m n ( m o d    a n ) x \equiv \begin{cases} m_1(\mod a_1)\\ m_2(\mod a_2)\\ ...\\ m_n(\mod a_n) \end{cases} xm1(moda1)m2(moda2)...mn(modan)
  • 可以通过进行 n − 1 n-1 n1 次迭代求解模线性方程求解,具体过程将在后续连载中进行讲解。

4、欧拉函数

1)互素

  • 两个数 a a a b b b 互素的定义为: g c d ( a , b ) = 1 gcd(a, b) = 1 gcd(a,b)=1,那么如何求不大于 n n n 且与 n n n 互素的数的个数呢?

朴素算法

  • 枚举i从1到n,当 g c d ( i , n ) = 1 gcd(i, n)=1 gcd(i,n)=1 时计数器加1,算法时间复杂度 O ( n ) O(n) O(n)

优化算法

  • 这里引入一个新的概念:用 φ ( n ) φ(n) φ(n) 表示不大于 n n n 且与 n n n 互素的数的个数,该函数以欧拉的名字命名,称为欧拉函数。
  • a)如果 n n n 是一个素数,即 n = p n = p n=p,那么 φ ( n ) = p − 1 φ(n) = p-1 φ(n)=p1(和所有小于 n n n 的都互素);
  • b)如果 n n n 是素数的 k k k 次幂,即 n = p k n = p^k n=pk,那么 φ ( n ) = p k − p k − 1 φ(n) = p^k - p^{k-1} φ(n)=pkpk1 (除了 p p p 的倍数其它都互素);
  • c)如果 m m m n n n 互素,那么 φ ( m n ) = φ ( m ) φ ( n ) φ(mn) = φ(m)φ(n) φ(mn)=φ(m)φ(n)(可以利用上面两个性质进行推导)。
  • 如果将 n n n 进行素因子分解如下:
    n = ∏ i = 1 k p i e i n = \prod_{i=1}^kp_i^{e_i} n=i=1kpiei
  • n n n 的欧拉函数表示为:
    φ ( n ) = ∏ i = 1 k p i e i − 1 ( p i − 1 ) φ(n) = \prod_{i=1}^kp_i^{e_i-1}(p_i-1) φ(n)=i=1kpiei1(pi1)
  • 前面已经讲到n的因子分解复杂度为 O ( k ) O(k) O(k),所以欧拉函数的求解就是 O ( k ) O(k) O(k)
  • 关于欧拉函数的性质和应用,后续章节还会展开来继续讲,本章先介绍欧拉函数的筛选法求解算法。

2)筛选法求解欧拉函数

  • 由于欧拉函数的表示法和整数的素数拆分表示法很类似,都可以表示成一些素数的函数的乘积,所以同样可以利用筛选法进行求解。
  • C++ 代码如下:

const int MAX_NUMBER = 10000010;
const int MAXP = 3163;                // (int)(sqrt(MAX_NUMBER*1.0) + 1);
#define LL long long

bool notprime[MAX_NUMBER];
int primes[MAXP];
int eula[MAX_NUMBER];

void eulaFilter() {
    
    
    int i, j;
    notprime[1] = 1;
    eula[1] = 1;

    // 1.枚举每个数
    for (i = 2; i < MAX_NUMBER; ++i) {
    
    
        if (!notprime[i]) {
    
    
            // 2.素数i的欧拉函数i-1
            eula[i] = i - 1;
            if (i < MAXP) {
    
    
                primes[++primes[0]] = i;
            }
            for (j = i + i; j < MAX_NUMBER; j += i) {
    
    
                notprime[j] = 1;
                // 3.非素数的欧拉函数为本身*素数分量(1-1/i)的乘积
                if (!eula[j]) eula[j] = j;
                eula[j] = eula[j] / i*(i - 1);
            }
        }
    }
}

3)欧拉定理和费马小定理

欧拉定理

  • n , a n,a n,a 为正整数,且 n , a n,a n,a 互素,则: a φ ( n ) ≡ 1 ( m o d    n ) a^{φ(n)} \equiv 1(\mod n) aφ(n)1(modn)

费马小定理

  • p p p 为素数, a a a 为正整数且和 p p p 互素,则: a p − 1 ≡ 1 ( m o d    p ) a^{p-1} \equiv 1(\mod p) ap11(modp)
  • 由于当n为素数时 φ ( n ) = p − 1 φ(n) = p-1 φ(n)=p1,可见费马小定理是欧拉定理的特殊形式。

4)扩展欧拉定理

  • 扩展欧拉定理表示如下:
    a b m o d    c = { a b m o d    φ ( c ) m o d    c g c d ( a , c ) = 1 ( 1 ) a b m o d    c g c d ( a , c ) ≠ 1 , b < φ ( c ) ( 2 ) a b m o d    φ ( c ) + φ ( c ) m o d    c g c d ( a , c ) ≠ 1 , b > = φ ( c ) ( 3 ) a^{b} \mod c = \begin {cases} a^{b \mod \varphi(c)} \mod c & { gcd(a,c)=1} & (1)\\ a^{b} \mod c & { gcd(a,c) \neq 1 ,b < \varphi(c)} & (2)\\ a^{b \mod \varphi(c) + \varphi(c)} \mod c & { gcd(a,c) \neq 1 ,b >= \varphi(c)} & (3)\\ \end {cases} abmodc=abmodφ(c)modcabmodcabmodφ(c)+φ(c)modcgcd(a,c)=1gcd(a,c)=1b<φ(c)gcd(a,c)=1b>=φ(c)(1)(2)(3)
  • 介于篇幅原因,具体的推导过程以及实际应用,将在后续章节进行连载讲解。

三、数论常用算法

a、Rabin-Miller 大素数判定

  • 对于一个很大的数 n n n(例如十进制表示有 100 100 100 位),如果还是采用试除法进行判定,时间复杂度必定难以承受,目前比较稳定的大素数判定法是 拉宾-米勒(Rabin-Miller)素数判定。
  • 算法推导和应用过程将在后续章节进行讲解。

b、Pollard-rho 大数因式分解

  • 有了大数判素,就会伴随着大数的因式分解,Pollard-rho 是一个大数分解的随机算法,能够在 O ( n 1 4 ) O(n ^{\frac 14} ) O(n41)的时间内找到 n n n 的一个素因子 p p p

c、Baby-Step Giant-Step 离散对数
a x ≡ b ( m o d    n ) a^x \equiv b (\mod n) axb(modn)

  • 对于如上方程,只有 x x x 是未知数, ( a , b , n ) (a,b,n) (a,b,n) 为常数,求的就是 x x x 的值。
  • 对于 Baby-Step Giant-Step 算法,会在后续章节进行讲解。

d、二次剩余

  • a a a n n n 都是常数,求一个最小的 x x x 满足:
    x 2 ≡ a ( m o d    n ) x^2 \equiv a (\mod n) x2a(modn)

e、RSA 算法

  • RSA 算法主要用于对敏感数据进行加密。


四、数论题集整理

1、素数和因数分解

题目链接 难度 解法
Largest prime factor ★☆☆☆☆ 素数筛选
The number of divisors ★☆☆☆☆ 因子数
七夕节 ★☆☆☆☆ 因子和
Happy 2004 ★☆☆☆☆ X^Y的因子和
Number Sequence ★☆☆☆☆ 循环节的经典问题
Beijing 2008 ★★☆☆☆ X^Y的因子和
f(n) ★★☆☆☆ 找规律+素数筛选
本原串 ★★☆☆☆ 整除性质 + 因子枚举
Special Prime ★★☆☆☆ 3n^2+3n+1 的素数判定问题
YAPTCHA ★★☆☆☆ 素数性质
Factorial Simplificat ★★★☆☆ 因式分解+树状数组+DFS
Gcd & Lcm game ★★★★☆ 因式分解+线段树

2、GCD && LCM

题目链接 难度 解法
hide handkerchief ★☆☆☆☆ 互素判定
GCD and LCM ★★☆☆☆ GCD和LCM性质 + 排列组合
Revenge of GCD ★★☆☆☆ 辗转相除+因子枚举
Least common multiple ★★★☆☆ GCD性质 + 完全背包

3、同余性质 、二分幂、循环节

题目链接 难度 解法
N!Again ★☆☆☆☆ 同余的乘法性质
Ice Rain ★★☆☆☆ 余数性质
The Two Note Rag ★★☆☆☆ 二分快速幂
Jacobi symbol ★★☆☆☆ 二分快速幂 + 二次剩余判定
Love you TenThous years ★★☆☆☆ 规律
TCE-frep number system ★★☆☆☆ 完全数
Perfect Squares ★★☆☆☆ 同余性 + 循环节
X mod f(x) ★★★☆☆ 利用同余原理进行动态规划
Interesting Fibonacci ★★★★☆ 复杂的循环节

4、模线性方程和逆元

题目链接 难度 解法
青蛙的约会 ★☆☆☆☆ 线性同余
Romantic ★☆☆☆☆ 线性同余
Robot ★★☆☆☆ 逆元
An easy problem ★★☆☆☆ 逆元
A/B ★★☆☆☆ 逆元入门题
A New Change Problem ★★☆☆☆ 同余推导
Central Meridian Number ★★☆☆☆ 线性同余+枚举
number theory ★★☆☆☆ 快速幂取模 + 欧几里德定理
Counting Problem ★★★☆☆ 逆元 + 组合数取模
Multiply game ★★★★☆ 树状数组 + 逆元

5、模线性方程组

题目链接 难度 解法
Chinese remainder theorem again ★★☆☆☆ 中国剩余定理 简化版
Strange Way to Express Integers ★★★☆☆ 中国剩余定理 模板题
Hello Kiki ★★★☆☆ 中国剩余定理 模板题
X问题 ★★★☆☆ 中国剩余定理 模板题
And Now, a Remainder from ★★★☆☆ 中国剩余定理 模板题

6、欧拉函数、欧拉定理、费马小定理

题目链接 难度 解法
2^x mod n = 1 ★☆☆☆☆ 费马小定理 简化版的
HeHe ★☆☆☆☆ 欧拉函数
GCD ★★☆☆☆ 欧拉函数
Become A Hero ★★☆☆☆ 筛选法求欧拉函数
The Euler function ★★☆☆☆ 筛选法求欧拉函数
The Luckiest number ★★★☆☆ 费马小定理
Calculation ★★★☆☆ 费马小定理
Description has only two Sentences ★★★☆☆ 费马小定理,我的题

6、容斥原理

题目链接 难度 解法
How many integers ★★☆☆☆ 容斥原理
Visible Trees ★★☆☆☆ 容斥原理
GCD Again ★★☆☆☆ 容斥原理
GCD ★★☆☆☆ 容斥原理
GCD ★★☆☆☆ 容斥原理
Coprime ★★★☆☆ 二分枚举+容斥原理
Sky Code ★★★☆☆ 容斥原理

7、大素数判定

题目链接 难度 解法
GCD & LCM Inverse ★★★☆☆ 拉宾米勒大数判素+dfs
Pseudoprime numbers ★★★☆☆ 拉宾米勒
Problem about GCD ★★★★☆ 拉宾米勒
Prime Test ★★★★☆ 拉宾米勒+Pollard-rho
RSA ★★★★☆ 拉宾米勒 + 线性同余

8、离散对数-Baby Step Gaint Step算法

题目链接 难度 解法
Discrete Logging ★★★☆☆ 基础
Mod Tree ★★★★☆ 扩展Baby Step Giant Step
Matrix Puzzle ★★★★★ Baby Step Giant Step + 高斯消元

9、其它

题目链接 难度 解法
Can you find it
GCD of Sequence
Sum Of Gcd
GCD Array
GCD pair
GCD
Gcd and Lcm
The sum of gcd
GCD?LCM!

猜你喜欢

转载自blog.csdn.net/WhereIsHeroFrom/article/details/111693538