浅谈数论

· · 算法·理论

由于本人是 xxs,数学学的不多,文章有错误还请多多谅解。

本文同步发布于博客。

质数

质数,也称素数。

若一个数 p 为质数,则它的因数只有 1, p

例如 2, 3 都是质数。

特别的,1 不是质数。

判断质数

判断一个数 p 是否是质数,需要判断 2\sim \sqrt p 是否有数是 p 的因数,若有,则说明 p 有除了 1, p 以外的因数,p 不是质数,否则,p 是质数。

时间复杂度 O(\sqrt p)

bool is_prime(int p)
{
    if(p == 1) return false;//特判,1 不是质数
    for(int i = 2;i * i <= p;i ++)
        if(p % i == 0) return false;//判断 p 是否有除了 1,p 以外的因数,如果有,p 不是质数
    return true;//如果 p 只有 1,p 两个因数,p 是质数
} 

质数筛

埃氏筛

埃氏筛,全称埃拉托斯特尼筛法。

埃氏筛的核心思想是:如果一个数 p 是质数,那么 p 的倍数的不是质数,即将所有质数的倍数都标记为合数,不需要标记合数的倍数

埃氏筛时间复杂度 O(n \log \log n),但是会重复标记(例如 62, 3 分别标记了一次),导致常数较大。

bool is_prime[N];//is_prime[i] 用来标记 i 是不是质数 
void sieve_primes(int n)
{
    for(int i = 1;i <= n;i ++) is_prime[i] = true;//初始化 
    is_prime[1] = false;//1 不是质数 
    for(int i = 2;i <= n;i ++)
    {
        if(is_prime[i] == false) continue;//因为只标记质数的倍数,所以遇到合数要跳过 
        for(int j = i * 2;j <= n;j += i)
            is_prime[j] = false;//标记 
    }
}

欧拉筛

欧拉筛,也称线性筛,是一种比埃氏筛更优秀的筛法。

欧拉筛的核心思想是:对于每个合数,只会被它的最小质因数给筛掉,从而避免重复标记。

欧拉筛需要多维护一个数组 primeprime_i 表示第 i 个质数。

欧拉筛需要用当前遍历到的 i 和当前筛出的质数 prime_j 标记合数,因为 i\times prime_j 一定是合数,先进行标记,然后,最重要的优化来了:如果 i\bmod prime_j = 0,说明 i 的最小质因数为 prime_j,此时应该停止标记。

欧拉筛的时间复杂度为 O(n)

bool is_prime[N];//is_prime[i] 用来标记 i 是不是质数 
int prime[N], tot;//tot 表示当前质数的个数,prime[i] 表示第 i 个质数 
void sieve_primes(int n)
{
    for(int i = 1;i <= n;i ++) is_prime[i] = true;//初始化 
    is_prime[1] = false;//1 不是质数 
    for(int i = 2;i <= n;i ++)
    {
        if(is_prime[i] == true) prime[++ tot] = i;//如果第 i 个数是质数,把它加入 prime 数组 
        for(int j = 1;j <= tot && prime[j] * i <= n;j ++)
        {
            is_prime[prime[j] * i] = false;//标记 
            if(i % prime[j] == 0) break;//当 prime[j] 是 i 的最小质因数是,停止标记 
        }
    }
}

整除与同余

整除

a 是非零整数,b 是整数。若存在一个整数 x,使得 b = a\times x,那么称 b 可被 a 整除a 可以 整除 b,记作 a\mid b,也可以说 ba 的倍数或 ab 的因数。例如 2 \mid 6,4\mid 8

整除具有以下性质:

同余

a,b 为两个整数,且他们的差 a - b 能被某个自然数 p 整除,则称 ab 关于模 p 同余,记作 a\equiv b \pmod p,意味着 a - b = p \times kk 为某个整数)。例如 36\equiv 1 \pmod 5,此时 36 - 1 = 35 = 5\times 7

对于整数 a,b,c 和自然数 n,m,对模 m 同余具有以下性质:

要注意的是,同余不满足同除性,即不满足 a\div n\equiv b\div n \pmod m

最大公约数

最大公约数指两个整数 a,b 公有的因数中最大的数,记作 \gcd(a,b)

辗转相除法

辗转相除法用来求两个数的最大公约数,又称欧几里得算法,其核心为:\gcd(x,y) = \gcd(y, x\bmod y)

证明:

辗转相除法的流程如下:

时间复杂度 O(\log \max(x,y))

int gcd(int x, int y)
{
    if(y == 0) return x;//y = 0 则返回答案 
    return gcd(y, x % y);//否则继续递归 
}

最小公倍数

最小公倍数指两个整数 a,b 公有的倍数中最小的数,记作 \text{lcm}(a,b)

定理:x\times y = \gcd(x, y) \times \text{lcm}(x, y)

根据以上定理,可以得出 \text{lcm}(x, y) = x\times y\div \gcd(x, y)

用辗转相除法求出 \gcd(x, y) 然后直接计算就行了,时间复杂度与辗转相除法一样。

int gcd(int x, int y)
{
    if(y == 0) return x;
    return gcd(y, x % y);
}
int lcm(int x, int y)
{
    return x * y / gcd(x, y);
}

欧拉定理

若正整数 a, n 互质,则:

a^{\varphi(n)}\equiv 1\pmod n

其中 \varphi(n) 为欧拉函数,表示 1\sim n 中与 n 互质的数的个数。

证明

x_1,x_2,\dots,x_{\varphi(n)}1\sim n 中与 n 互质的数。

这时,我们发现 a\times x_1, a\times x_2, \dots, a\times x_{\varphi(n)} 具有这个性质:每个数 \bmod n 两两不同,且余数与 n 互质。而且,x_1, x_2, \dots, x_{\varphi(n)} 也具有这个性质。

有了这个性质,将 a\times x_1, a\times x_2, \dots, a\times x_{\varphi(n)}\bmod n 后一定是 \varphi(n) 个不同的与 n 互质的数,那么:

x_1\times x_2\times \dots\times x_{\varphi(n)}\equiv a\times x_1\times a\times x_2\times \dots\times a\times x_{\varphi(n)}\pmod n\\ 1\equiv a^{\varphi(n)}\pmod n

费马小定理

若整数 p质数p 与整数 a 互质,则满足:

a^{p - 1}\equiv 1\pmod p

证明

因为 p 是质数,那么 \varphi(p) = p - 1,这时就转化成了欧拉定理的一种情况,由于满足欧拉定理,费马小定理一定成立。

裴蜀定理

对于二元一次不定方程 a\times x + b\times y = c,有解当且仅当 \gcd(a, b)\mid c

证明

d = \gcd(a, b),显然 d\mid a, d\mid b

d\mid a\times x, d\mid b\times y

那么,要使方程成立,必须满足 d\mid c

扩展欧几里得算法

扩展欧几里得算法是用来求解 a\times x + b\times y = \gcd(a, b) 的一种算法。

首先,根据数论中的相关定理,此方程一定有解。

因为 \gcd(a, b) = \gcd(b, a\bmod b),所以 a\times x + b\times y = \gcd(a, b) = \gcd(b, a\bmod b) = x \times b + y\times (a\bmod b)= x\times b + y\times (a - \lfloor \frac{a}{b} \rfloor \times b) = y\times a + (x - \lfloor \frac{a}{b} \rfloor \times y)\times b

可以根据欧几里得算法递归,当 b = 0 时,可以得出 x = 1, y = 0,。

扩展欧几里得算法流程如下:

扩展欧几里得算法时间复杂度与欧几里得算法一样,为 O(\log \max(a, b))

void exgcd(int a, int b)
{
    if(b == 0)
    {
        x = 1, y = 0;//b = 0 时,x = 1,y = 0
        return;
    }
    exgcd(b, a % b);//递归
    int tmp = x;
    x = y;
    y = tmp - a / b * y;
}

逆元

a\times x\equiv 1\pmod p,并且 a, p 互质,则称 ap 的乘法逆元为 x,记作 a^{-1}

费马小定理求逆元

注意,当模数 p 是质数且 a\ne 0 时,才可使用费马小定理求逆元。

根据费马小定理:

a^{p - 1}\equiv 1\pmod p

因此:

a\times a^{p - 2}\equiv 1\pmod p\\

很明显,此时 a^{p - 2} 即为 a^{-1}

使用快速幂求出 a^{p - 2}\bmod p 即可,时间复杂度 O(\log p)

int qpow(int a, int b, int mod)
{
    int ans = 1;
    while(b)
    {
        if(b & 1) ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}//快速幂模板
int get_inv(int a, int p)
{
    return qpow(a, p - 2, p);//求出逆元
}

扩展欧几里得算法求逆元

但是,求解出来的 $x$ 可能是负数,需要通过累加 $p$ 来处理一下。 ```cpp void exgcd(int a, int b)//扩展欧几里得算法模板 { if(b == 0) { x = 1, y = 0; return; } exgcd(b, a % b); int tmp = x; x = y; y = tmp - a / b * y; } int get_inv(int a, int p) { exgcd(a, p); return (x % p + p) % p;//使 x 变为最小正整数解 } ``` ### 递推求逆元 设 $p = k\times i + r, r < i, 1 < i < p$,然后把这个式子放在 $\bmod p$ 意义下可以得到: $$ k\times i + r \equiv 0\pmod p $$ 将两边同时乘上 $i^{-1}$ 和 $r^{-1}$ 可以得到: $$ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ k\times r^{-1} + i^{-1}\equiv 0\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \pmod p\\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ i^{-1}\equiv -k\times r ^ {-1}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \pmod p\\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ i^{-1}\equiv - \lfloor \frac{p}{i} \rfloor \times (p\bmod\ i)^{-1}\pmod p $$ 通过这个式子即可从前面的数推出当前数的逆元。 ```cpp void get_inv(int n, int p) { inv[1] = 1;//初始值 for(int i = 2;i <= n;i ++) inv[i] = (p - p / i) * inv[p % i] % p;//递推式 } ``` ## 中国剩余定理 中国剩余定理用来求解以下同余方程组: $$ \begin{cases} x\equiv a_1&\pmod {b_1}\\ x\equiv a_2&\pmod {b_2}\\ \dots\\ x\equiv a_n&\pmod {b_n} \end{cases} $$ 其中 $b_i$ 两两互质。 根据余数的可加性,只有求出以下 $n$ 个方程组的解,则可以求出原方程的解: $$ \begin{cases} x_1\equiv a_1&\pmod {b_1}\\ x_1\equiv 0&\pmod {b_2}\\ \dots\\ x_1\equiv 0&\pmod {b_n} \end{cases} \begin{cases} x_2\equiv 0&\pmod {b_1}\\ x_2\equiv a_2&\pmod {b_2}\\ \dots\\ x_2\equiv 0&\pmod {b_n} \end{cases} \dots \begin{cases} x_n\equiv 0&\pmod {b_1}\\ x_n\equiv 0&\pmod {b_2}\\ \dots\\ x_n\equiv a_n&\pmod {b_n} \end{cases} $$ 原方程解 $x = \sum^{n}_{i = 1} x_i$。 设当前求解的方程组为第 $c$ 个: $$ \begin{cases} x_c\equiv 0&\pmod {b_1}\\ x_c\equiv 0&\pmod {b_2}\\ \dots\\ x_c\equiv a_c&\pmod {b_c}\\ \dots\\ x_c\equiv 0&\pmod {b_n} \end{cases} $$ 将上方程组转化为: $$ \begin{cases} y_c\equiv 0&\pmod {b_1}\\ y_c\equiv 0&\pmod {b_2}\\ \dots\\ y_c\equiv 1&\pmod {b_c}\\ \dots\\ y_c\equiv 0&\pmod {b_n} \end{cases} $$ 那么,$x_c = y_c\times a_c$。 由于 $b_i$ 两两互质,可得 $y_c\bmod \frac{\prod_{i = 1}^{n} b_i}{b_c} = 0$。设 $\frac{\prod_{i = 1}^{n} b_i}{b_c}$ 为 $l_c$,则 $b_c = l_c\times w_c$。 根据以上方程组,可知 $l_c\times w_c\equiv 1\pmod {b_c}$,显然 $w_c$ 就是 $l_c$ 的逆元,直接使用扩展欧几里得算法求就行了。 方程解 $x = \sum_{i = 1}^{n} a_c\times l_c\times l_c^{-1}$。 由于需要求的是方程的**最小正整数解**,需要累加 $\prod_{i = 1}^{n} a_i$ 并取模。 ```cpp void exgcd(int a, int b)//扩展欧几里得算法模板 { if(b == 0) { x = 1, y = 0; return; } exgcd(b, a % b); int tmp = x; x = y; y = tmp - a / b * y; } for(int i = 1;i <= n;i ++) { int qwq = cj / a[i];//cj 为所有数的乘积,qwq 即为 l_c exgcd(qwq, a[i]);//求出逆元 ans = (ans + qwq * b[i] * x) % cj;//ans 为最终答案 } cout << (ans + cj) % cj;//转化成最小正整数解 ``` ## 欧拉函数 欧拉函数 $\varphi(n)$ 表示 $1\sim n$ 中与 $n$ 互质的数的个数,特别的,$\varphi(1) = 1$。 欧拉函数是一个积性函数,若 $n\perp m$,则 $\varphi(n\times m) = \varphi(n)\times \varphi(m)$。 设 $n$ 分解质因数后为 $\prod_{i = 1}^mp_i^{q_i}$,则 $\varphi(n) = n\times \prod_{i = 1}^m (1 - \frac{1}{p_i})$。 ### 证明 - 由于欧拉函数是一个积性函数,只需要考虑 $p_i^{q_i}$ 的取值就能计算所有数的欧拉函数了。 - 若一个数与 $p_i^{q_i}$ 不互质,则这个数一定是 $p_i$ 的倍数,那么 $1\sim p_i^{q_i}$ 中有 $p_i^{q_i - 1}$ 个数和 $p_i^{q_i}$ 不互质,反过来和 $p_i^{q_i}$ 互质的数的个数为 $p_i^{q_i} - p_i^{q_i - 1} = p_i^{q_i}\times (1 - \frac{1}{p_i})$,则 $\varphi(n) = n\times \prod_{i = 1}^m (1 - \frac{1}{p_i})$。 $O(\sqrt n)$ 求解 $\varphi(n)$: ```cpp phi = n;//phi 表示 phi(n) for(int i = 2;i * i <= n;i ++) if(n % i == 0) { phi = phi / i * (i - 1);//乘上 (1 - 1 / p) while(n % i == 0) n /= i;//把 n 中的 i 全部筛去 } if(n != 1) phi = phi / n * (n - 1); ``` 但是,以上算法如果计算多个数的欧拉函数,容易超时,需要使用欧拉筛优化,时间复杂度 $O(n)$: ```cpp phi[i] = 1; for(int i = 2;i <= n;i ++) { if(phi[i] == 0) p[++ tot] = i, phi[i] = i - 1;//若 i 是质数,则 phi(i) = i - 1 for(int j = 1;j <= tot && i * p[j] <= n;j ++) { if(i % p[j] == 0)//i 与 p[j] 不互质的情况 { phi[i * p[j]] = phi[i] * p[j]; break; } else phi[i * p[j]] = phi[i] * (p[j] - 1);//i 与 p[j] 互质的情况 } } ```