数论

生来就可爱ヽ(ⅴ<●) 提交于 2019-11-26 19:35:43

以下仅供个人整合资料使用,非本人原创

倍数

对于自然数 a, b,如果存在自然数 k,使得 ka = b,那么 b是 a 的倍数,称 a 整除 b,b 能被 a 整除,记做 a|b

一个数有无穷多个倍数
所有数都是自身和 1 的倍数
倍数具有传递性
在 [1, n] 范围内的 x 的倍数有 ⌊nx⌋个
约数

对于自然数 a, b,如果 b 是 a 的倍数,那么 a 是 b 的约数,又称因数

所有数的约数都包含自身和(或)1

一个自然数只有有限个约数,约数的个数通常记为 d(n)
打个表看看
n 1 2 3 4 5 6 7 8 9 10
d(n) 1 2 2 3 2 4 2 4 3 4
打表
int d[MAXN];
// 计算出 [1, n] 中每个数的约数个数
// 复杂度 O(n log n)
void calc_divisors(int n) {
    for (int i = 1; i <= n; ++i) {
        // 枚举 i 的所有倍数
        for (int j = i; j <= n; j += i) {
            d[j]++;
        }
    }
}

素数与合数

如果一个数的约数只有两个(1 和自身),那么称它为素数(或者质数)

如果一个数有非平凡(除了 1 和自身以外)的约数,就称它为合数
1 既不是素数也不是合数!
素性判定:
// 判断一个数是否为素数
bool is_prime(int x) {
    // 特判 1 的情况
    if (x == 1) return false;
    // 从 2 枚举到 √x,判断是否能整除
    for (int i = 2; i * i <= x; ++i) {
        // 如果能整除,则为合数
        if (x % i == 0) return false;
    }
    // 如果都不能整除,则为素数
    return true;
}

朴素筛法:

int vis[MAXN], p[MAXN], cnt;
// 筛出 [2, n] 之间所有的素数
// 复杂度:O(n log n)
void naive_sieve(int n) {
    for (int i = 2; i <= n; ++i) {
        if (!vis[i]) p[cnt++] = i;
        // 筛掉 i 除了本身以外的所有倍数
        for (int j = i + i; j <= n; j += i) {
            vis[j] = 1;
        }
    }
}

埃式筛法:

int vis[MAXN], p[MAXN], cnt;
// 筛出 [2, n] 之间所有的素数
// 复杂度:O(n log log n)
void eratosthenes_sieve(int n) {
    for (int i = 2; i <= n; ++i) {
        if (!vis[i]) {
            p[cnt++] = i;
            // 筛掉 i 除了本身以外的所有倍数
            for (int j = i + i; j <= n; j += i) {
                vis[j] = 1;
            }
        }
    }
}

欧拉筛法(线性筛):

int vis[MAXN], p[MAXN], cnt;
// 筛出 [2, n] 之间所有的素数
// 复杂度:O(n)
void euler_sieve(int n) {
    for (int i = 2; i <= n; ++i) {
        if (!vis[i]) p[cnt++] = i;
        for (int j = 0; i * p[j] <= n; ++j) {
            vis[i * p[j]] = 1;
            // 保证 p[j] 是 i*p[j] 最小的素因子
            if (i % p[j] == 0) break;
        }
    }
}

唯一分解定理(算术基本定理)

 (唯一分解定理) 每个大于 1 的自然数均可写为素数的积,而且这些素因子按大小 排列之后,写法仅有一种方式。

105 = 3 × 5 × 7

200 = 2 × 2 × 2 × 5 × 5 = 23 × 5 2

写成素因子的幂的积,x = p1 k1 p2 k2 p3 k3 · · · pn kn

如果不含某个素因子,可以看作它的次数为 0 

乘法就是对应素因子的次数相加,除法就是对应素因子的次 数相减

如果一个数所有的素因子次数都大于等于另一个数对应的素 因子次数,那么就可以被它整除

素因数分解

// 将 x 分解素因数,结果从小到大储存在 p[] 中,返回素因子的个数
int factorize(int x, int p[]) {
    int cnt = 0;
    // 从 2 枚举到 √x,判断是否为约数
    for (int i = 2; i * i <= x; ++i) {
    // 如果找到一个约数,就不断从 x 中除去
        while (x % i == 0) {
            p[cnt++] = i;
            x /= i;
        }
    }
    // 如果 x > 1,则说明剩下的 x 是素数,也要放进数组
    if (x > 1) p[cnt++] = x;
    return cnt;
}

最大公约数 (gcd)

定义 (最大公约数) 两个自然数所共有的约数中最大的一个,称为它们的最大公约数 (Greatest Common Divisor)

利用唯一分解定理及推论,可以发现 gcd 就是两个数对应的 素因子次数取 min 后得到的数

实际应用中,通常使用辗转相除法来计算 gcd

辗转相除法(欧几里得算法)

引理 (辗转相除原理)

定义对于任意自然数 a,gcd(0, a) = gcd(a, 0) = a。则对于任意 自然数 a, b,满足 gcd(a, b) = gcd(a, b mod a) 

利用这个性质,可以将 a, b 两者中较大的一个不断缩小,直 到变为零

每次大的数对小的数取模,至少缩小一半,所以复杂度为 O(log max(a, b))

辗转相除法

// 循环实现
int gcd(int a, int b) {
    while (a>0) {
        int t=b% a;
        b= a;
        a= t;
    }
    return b;
}
// 递归实现
int gcd(int a, int b) {
    if (a ==0) return b;
    return gcd(b% a, a);
}

最小公倍数 (lcm)

定义 (最小公倍数) 两个自然数所共有的倍数中最小的一个,称为它们的最小公倍数 (Least Common Multiple)

利用唯一分解定理及推论,可以发现 lcm 就是两个数对应 的素因子次数取 max 后得到的数

不需要另外去求,只要用一个公式就能转化成 gcd 问题 

lcm(a, b) = ab gcd(a, b)

可以用唯一分解定理来证明

扩展欧几里得算法

定理 (裴蜀定理)

对于任意整数 a, b,存在无穷多组整数对 (x, y) 满足不定方程 ax + by = d,其中 d = gcd(a, b)

在求 gcd(a, b) 的同时,可以求出(关于 x, y 的)不定方程 ax + by = d 的一组整数解

考虑递归计算:假设已经算出了 (b%a, a) 的一组解 (x0, y0) 满足 (b%a)x0 + ay0 = d

可以得到 (b − a ⌊ b a ⌋ )x0 + ay0 = d • 整理得到 a(y0 − ⌊ b a ⌋ x0) + bx0 = d

为了方便,交换 x0 和 y0,得到 a(x0 − ⌊ b a ⌋ y0) + by0 = d

扩展欧几里得算法

// 返回 gcd(a, b) 和方程 ax + by = gcd(a, b) 的一组解
int gcd(int a, int b, int &x, int &y) {
    if (a == 0) {
        // gcd(0, b) = b,显然 0 · 0 + 1 · b = b 满足条件
        x = 0, y = 1;
        return b;
    }
    // 这里交换了 x0 和 y0
    int d = gcd(b % a, a, y, x);
    // x = x_0 - b / a \times y_0
    // y = y_0
    x -= b / a * y;
    return d;
}

取模运算

对于自然数 a,正整数 m,a mod m = a − m ⌊ a m ⌋也称取余,因为实际上就是整除得到的余数a mod m ∈ [0, m)

取模运算和除法一样慢 

尽量不要负数取模

模意义下的数和运算

如果 a mod m = b mod m,可以记做 a ≡ b (mod m) 

所有自然数都能用 [0, m) 之间的整数来“代表” 

可以把取模推广到负数,a mod m = b(b ∈ [0, m)) ⇔ 存在整 数 k,满足 a = km + b

我们可以建立一个新的数字运算体系,其中只有 0 ∼ m − 1 这 m 个数,然后建立四则运算等基本规则

两个数相加,如果超出了 m − 1,就从 0 开始再往上加,相 乘与相加类似 

两个数相减,如果小于 0,就从 m − 1 再往下减

32 位无符号整数(unsigned int)的运算实际上是对 2 32 取 模的

运算的性质

(a + b)%m = (a%m + b%m)%m

(a − b)%m = (a%m − b%m)%m

(a × b)%m = (a%m) × (b%m)%m

假如题目要求最终答案对 m 取模,那么(为了防止数字过 大)可以在每一步运算中都取模

乘法逆元

 加法、减法、乘法都定义好了,除法怎么做呢?

可以不断将被除数加上 m,直到可以除尽,但是这样做效 率太低了

回想起以前学习分数时,除以一个数,可以转化成乘它的倒 数

类似地,我们可以试着为除数找到一个乘法逆元,即满足 xx−1 ≡ 1 (mod m) 的整数 x −1

当且仅当 m 为素数时,每个数都有唯一的乘法逆元,因此 大部分 OI 题中的模数都是素数

费马小定理

定理 (费马小定理) 对于任意质数 p 和正整数 a < p,有 a p−1 ≡ 1 (mod p) 证明.

易证,a, 2a, 3a, . . . ,(p − 1)a (mod p) 可以取遍 1 到 p − 1 之 间的所有数

于是 a · 2a · 3a · · ·(p − 1)a ≡ (p − 1)! (mod p)

由于 p 是素数,和 1 ∼ p − 1 之间的数都互素,所以等式两 边可以同时除以 (p − 1)!

就得到了 a p−1 ≡ 1 (mod p)

快速求逆元

有了费马小定理,就可以快速求出一个数的乘法逆元

由于 a p−2 · a ≡ a p−1 ≡ 1 (mod p),所以 a p−2%p 就是逆元

可以通过快速幂来计算,复杂度 O(log p)

另外,也可以用扩展欧几里得算法来求逆元,求出 ax + py = 1 的一组解,x%p 就是逆元

中国剩余定理 (CRT)

有若干个人列队,三个一排会多出两个人,五个一排多三个 人,七个一排多两个人,求人数至少是多少?

本质上是解一元线性同余方程组

x ≡ a1 (mod m1)

x ≡ a2 (mod m2)

. . .

x ≡ an (mod mn)

当 m 两两互素时,一定有解

 

我们来尝试构造出这个解

设 M = m1 × m2 × · · · × mn,Mi = M mi

Mi 只有在模 mi 的时候不为零,模其他的 m 都为零

由于我们想在模 mi 时得到 ai,所以需要乘上 aiti,其中 ti 是 Mi 在模 mi 意义下的乘法逆元

最终得到 a1t1M1 + a2t2M2 + · · · + antnMn,再对 M 取模, 就得到了最小的自然数解,加上 kM 就是通解

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!