4、数学
4.1 初等数论
4.1.1 同余式
同余式是指两个整数除以同一个数的余数相等,也就是说,如果 \(a\) 和 \(b\) 是两个整数,\(m\) 是一个正整数,当 \(a\) 除以 \(m\) 的余数等于 \(b\) 除以 \(m\) 的余数时,我们说 \(a\) 和 \(b\) 对模 \(m\) 同余,记作 \(a \equiv b (\mod m)\)。
例如,当 \(m=3\) 时,\(10 \equiv 1 (\mod 3)\),因为 \(10\) 除以 \(3\) 的余数是 \(1\),而 \(1\) 除以 \(3\) 的余数也是 \(1\)。
同余式在数论中有很多应用,比如求逆元、快速幂算法等都会用到同余式的性质。
4.1.2 欧拉定理和欧拉函数
欧拉定理和欧拉函数是数论中的两个重要概念。
欧拉定理:对于任意正整数 \(a\) 和 \(n\),若 \(a\) 和 \(n\) 互质,则 \(a^{\varphi(n)}\equiv1\pmod{n}\),其中 \(\varphi(n)\) 表示 \(n\) 的欧拉函数。
欧拉函数:对于任意正整数 \(n\),欧拉函数 \(\varphi(n)\) 表示小于等于 \(n\) 的正整数中与 \(n\) 互质的数的个数。
例如,对于 \(n=8\),小于等于 \(8\) 且与 \(8\) 互质的数为 \(1,3,5,7\),因此 \(\varphi(8)=4\)。
以下是求欧拉函数和使用欧拉定理求逆元的 C++ 代码示例:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
4.1.3 费马小定理
费马小定理是指,若 \(p\) 为质数,\(a\) 是整数且 \(p\) 不整除 \(a\),则 \(a^{p-1} \equiv 1 \pmod{p}\)。这可以表示为 \(a^p \equiv a \pmod{p}\),其中 \(a\) 与 \(p\) 互质。
例如,取 \(p = 7\) 和 \(a = 3\),则 \(3^6 \equiv 1 \pmod{7}\),即 \(3^7 \equiv 3 \pmod{7}\)。
费马小定理是许多数论问题的基础,包括求解逆元和判断质数等。
下面是使用费马小定理求解逆元的 C++ 代码示例:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
在上面的代码中,fast_pow
函数是快速幂算法,可以在 \(O(\log k)\) 的时间内求出 \(a^k\) 模 \(m\) 的值。
inverse
函数接受两个参数 \(a\) 和 \(m\),其中 \(a\) 是需要求逆元的数,\(m\) 是模数。如果 \(a\) 与 \(m\) 不互质,则无法求出逆元,此时返回 -1。如果 \(a\) 与 \(m\) 互质,则可以根据费马小定理得到 \(a^{m-1} \equiv 1 \pmod m\),从而得到 \(a^{-1} \equiv a^{m-2} \pmod m\),然后调用 fast_pow
函数求解即可。
在主函数中,首先读入 \(a\) 和 \(m\),然后调用 inverse
函数求出逆元。如果逆元不存在,则输出 "No inverse exists.",否则输出逆元的值。
4.1.4 威尔逊定理
威尔逊定理(Wilson's theorem)是一个关于质数的定理,它可以用来判断一个数是否为质数,或者判断一个数在模意义下是否有逆元。威尔逊定理的表述如下:
若 \(p\) 是一个质数,则 \((p-1)! \equiv -1 \pmod{p}\)。
因此,如果我们需要判断一个数 \(x\) 是否为质数,可以计算 \((x-1)!\) 在模 \(x\) 意义下的值,如果等于 \(x-1\),则 \(x\) 是一个质数。如果我们需要在模 \(p\) 意义下求一个数 \(x\) 的逆元,可以根据威尔逊定理得到:
但需要注意的是,威尔逊定理只适用于质数,对于合数是不成立的。
下面是使用威尔逊定理判断一个数是否为质数的 C++ 代码实现:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
4.1.5 裴蜀定理
裴蜀定理,也称为贝祖定理,是指对于任意给定的两个整数 \(a\) 和 \(b\),它们的最大公约数 \(gcd(a,b)\),必定可以表示成 \(ax+by\) 的形式,其中 \(x\) 和 \(y\) 为整数。这个定理的应用非常广泛,比如可以用来判断两个整数是否互质,以及求解一元线性不定方程等。
以下是使用扩展欧几里得算法求解裴蜀定理的 C++ 代码:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
使用示例:
输入:
C++ | |
---|---|
1 |
|
输出:
C++ | |
---|---|
1 |
|
4.1.6 逆元
逆元是指对于模数m,如果a和m互质,那么存在一个整数b,使得ab ≡ 1(mod m),b就是a的逆元,通常用a^{-1}表示。
在数学中,求a的逆元的方法有欧几里得扩展算法和费马小定理求逆元两种常见方法。
1.欧几里得扩展算法
欧几里得扩展算法又称扩展欧几里得算法,可以在O(log n)的时间内求出任意一组整数a、b的最大公约数gcd(a, b),并找到整数x、y,使它们满足贝祖等式ax + by = gcd(a, b)。
根据贝祖等式,我们可以通过不断的变形得到逆元的求解公式:
ax + my = 1,其中m为模数,因为ax ≡ 1 (mod m),所以x就是a在模m意义下的逆元。
2.费马小定理求逆元
费马小定理指出,对于任何一个质数p,对于整数a(a不是p的倍数),有a^{p-1} \equiv 1 \pmod{p},因此a的逆元就是a^{p-2}。当p不是质数时,这个结论不再成立,但是可以通过扩展欧几里得算法来求解逆元。
下面给出使用扩展欧几里得算法求逆元的C++代码实现:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
上述代码实现了使用扩展欧几里得算法来求解a在模m下的逆元。其中exgcd函数是扩展欧几里得算法的具体实现,mod_inv函数是求逆元的接口函数,它在使用exgcd函数求得x和y后,判断是否存在逆元,若存在则返回x在模m下的结果,否则返回-1。
需要注意的是,如果在模m下不存在a的逆元,那么会返回-1。在使用逆元时,需要对该返回值进行判断。
4.1.7 扩展欧几里得算法
扩展欧几里得算法是求解线性同余方程 ax ≡ b (mod m) 的一种方法,其中 a、b 和 m 都是整数,且 a 和 m 互质。该算法除了求解方程的解 x 外,还能够求解方程 ax + my = b 的一组整数解 x 和 y。其基本思想是通过递归调用的方式,利用欧几里得算法求解出最大公约数,并沿途记录下一些系数,最终得到方程的一个特解和通解。
以下是扩展欧几里得算法的 C++ 代码实现:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
运行结果:
C++ | |
---|---|
1 2 |
|
以上代码中,extended_euclidean
函数的输入为两个整数 a
和 b
,输出为最大公约数 gcd
和贝祖等式 ax + by = gcd(a, b)
的解 x
和 y
。函数的返回值为最大公约数 gcd
。
4.1.8 孙子定理(即中国剩余定理)
孙子定理,又称中国剩余定理,是解决同余方程组问题的一种方法。它适用于一般形式的同余方程组,方程的系数不必是互质的。
假设有 \(n\) 个方程,其同余条件为:$$ x\equiv a_1\pmod {m_1}, x\equiv a_2\pmod {m_2}, \cdots, x\equiv a_n\pmod {m_n} $$ 其中 \(a_1,a_2,\cdots,a_n\) 和 \(m_1,m_2,\cdots,m_n\) 两两互不相同,我们需要求出 \(x\) 的值。根据孙子定理,这个问题等价于:$$ x\equiv r_1\pmod {M_1}, x\equiv r_2\pmod {M_2}, \cdots, x\equiv r_n\pmod {M_n} $$ 其中 \(r_i\) 和 \(M_i\) 的定义如下:$$ M=\prod_{i=1}^{n}m_i, M_i=\frac{M}{m_i}, r_i\equiv M_i^{-1}\pmod {m_i} $$ 其中 \(M_i^{-1}\) 表示 \(M_i\) 在模 \(m_i\) 意义下的逆元。然后根据同余方程组的性质,\(x\) 可以表示为:$$ x=\sum_{i=1}^{n}r_iM_ix $$ 我们只需要求出 \(M_i\) 的逆元和 \(r_i\),就可以根据上述式子求出 \(x\) 的值。
以下是使用 C++ 实现孙子定理的代码示例:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
以上代码实现了孙子定理,求解形如 x ≡ a1 (mod m1),x ≡ a2 (mod m2),...,x ≡ an (mod mn) 的同余方程组。其中 a
是余数,m
是模数,x
是方程的解。sunzi
函数求解两个同余方程的解,使用扩展欧几里得算法求解逆元。最后,遍历所有的同余方程,使用 x
和 M
分别记录当前的解和模数,然后更新 x
和 M
,得到所有同余方程的解。如果无解,则返回 -1。
4.2 组合数学
4.2.1 可重集排列
可重集排列指的是将一个由\(n\)个元素组成的可重集合理解为一个长度为\(n\)的序列,并对该序列进行全排列。假设该可重集合中元素\(a_i\)出现的次数为\(c_i\),则该可重集合的不同排列数为:
其中\(c_1+c_2+\cdots+c_n\)为该可重集合的元素总数。
可以使用回溯算法解决可重集排列问题。遍历所有的排列,对于每个位置,枚举该位置可以填入哪些元素,然后进入下一层递归。当排列长度达到\(n\)时,即可将该排列加入结果集中。需要注意的是,对于每个位置,不能选择该元素的数量超过\(c_i\)个。在实现时,可以使用一个哈希表记录每个元素的数量,每次选择元素后将该元素的数量减1,递归完成后再将该元素的数量加1,恢复现场。
以下是使用 C++ 实现可重集排列的代码:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
|
在这个代码中,我们首先定义了两个函数 fact
和 repeat_factorial
分别用于计算阶乘和重复因子的积。其中,fact
函数用于计算阶乘,repeat_factorial
函数用于计算可重集合的重复因子的积。接下来,我们定义了函数 permutation
用于计算可重集合的全排列数量,并使用函数 generate_permutation
生成可重集合的全排列。最后,我们在主函数中测试了代码的正确性。
4.2.2 可重集组合
可重集组合问题指从一个包含 n 种元素的可重集合中选出 k 个元素,使得元素的顺序不考虑,且每个元素可以重复选取多次的方案数。这个问题可以使用类似于背包问题的动态规划思路来解决。
具体来说,设 dp[i][j] 表示从前 i 个元素中选取 j 个元素的可重集合的方案数,其中第 i 个元素可以选取多次。则有以下状态转移方程:
dp[i] [j] = dp[i-1] [j] + dp[i] [j-1]
其中 dp[i-1] [j] 表示不选第 i 个元素,dp[i] [j-1] 表示至少选一次第 i 个元素。需要注意的是,在 dp[i] [j-1] 中,因为第 i 个元素可以选取多次,所以实际上是选取的是前 i 个元素中选取 j-1 个元素的可重集合,再加上选取了第 i 个元素。因此,dp[i] [j-1] 应该被拆分成 dp[i-1] [j-1] + dp[i] [j-2],其中 dp[i-1] [j-1] 表示选取了一次第 i 个元素,dp[i] [j-2] 表示选取了至少两次第 i 个元素。
最终,可重集合的方案数为 dp[n] [k]。
以下是使用动态规划实现可重集组合问题的 C++ 代码示例:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
其中,w[i]
表示第i个元素的重量,c[i]
表示第i个元素的价值,f[i]
表示容量为i时的最大价值。在循环中,我们对于每一个元素,从其自身重量开始向容量m遍历,更新每一个容量下的最大价值。最终,f[m]即为所求的可重集合组合问题的答案。
4.2.3 错排列、圆排列
错排列(Derangement)和圆排列(Circular Permutation)是排列问题中的两个经典问题。
错排问题是指在有n个元素的排列中,元素不在其原有的位置上的排列总数,即排列中每个位置的元素都与原序列中对应位置的元素不同。错排问题也叫重排列问题。
圆排列问题是指在有n个元素的排列中,任意一个位置都可以作为排列的开头,而这些不同的排列被认为是等价的。
下面分别介绍它们的计算方法。
错排列
错排问题可以使用递推或数学公式求解。下面分别介绍两种方法。
1.递推方法
设\(f(n)\)为有n个元素的错排数,则:
当\(n=0\)时,\(f(0)=1\); 当\(n=1\)时,\(f(1)=0\); 当\(n=2\)时,\(f(2)=1\); 当\(n>2\)时,第一个元素有\(n-1\)种不同的放置方法,放置第一个元素后,问题就变成了有\(n-1\)个元素的错排问题。此时,放置第一个元素后剩余的\(n-1\)个元素不能放在其对应的位置上,共有\(f(n-1)\)种不同的错排方法。因此,有如下递推关系式:
2.数学公式方法
根据排列组合知识,有\(n\)个元素的全排列共有\(n!\)种,而错排问题实际上就是求\(n\)个元素的全排列中,元素恰好不在其原来位置上的排列数。设有\(D(n)\)种这样的排列,则:
上式中,第\(i\)项是指\(n\)个元素中恰好有\(i\)个元素不在其原来位置上的排列数。
圆排列
圆排列指的是将 \(n\) 个不同的物品排成一个环,且不考虑环的方向。与排列问题类似,圆排列的总数可以用容斥原理求得。
对于 \(n\) 个物品的圆排列问题,总的排列数为 \((n-1)!\)。然而,由于这些排列是围成一个圆形的,所以我们需要除以 \(n\),以消除圆排列的等价性。也就是说,若存在 \(k\) 个排列在圆上重合,那么这些排列都是等价的,即只算一个排列。因此,总数为 \(\frac{(n-1)!}{n} = (n-2)!\)。
若我们要计算 \(n\) 个物品的圆排列中,至少有 \(m\) 个物品固定不动的排列数,我们可以先选定 \(m\) 个物品,它们必须排在固定位置上。这些物品固定不动,因此只需要在 \(n-m\) 个剩余物品中选出 \((n-m-1)!\) 个排列即可,最后再将 \(m\) 个物品插入到 \((n-m-1)!\) 个排列的不同位置上。因此,\(n\) 个物品的圆排列中,至少有 \(m\) 个物品固定不动的排列数为 \((n-m-1)!m\)。
下面是圆排列的 C++ 代码实现,其中使用了阶乘预处理的方法,可以提高计算效率:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
代码中,factorial
函数计算 \(n\) 的阶乘,circular_permutation
函数计算圆排列的数量,即 \((n-1)!\)。最后读入 \(n\),输出圆排列的数量。
4.2.4 鸽巢原理
鸽巢原理,也称为抽屉原理,是数学中的一种基本定理。它表明,如果将多于 \(n\) 个物体放入 \(n\) 个鸽巢中,则必定有一个鸽巢中至少放了两个物体。鸽巢原理可以用于求解各种组合问题,是许多算法的基础。
举个例子,假设有 \(n+1\) 个人要参加一个大小为 \(n\) 的会议,每个人要么参加要么不参加。那么根据鸽巢原理,至少有两个人要么都参加,要么都不参加,因为有 \(n+1\) 个人,而只有两个选择,参加或不参加。
在编程竞赛中,鸽巢原理常用于解决如下问题:
- 求多个数中出现次数大于 \(\frac{n}{k}\) 的数。
- 求多个数中最大和最小值的最小距离。
- 求一个字符串中是否有长度为 \(k+1\) 的子串出现至少 \(k\) 次。
在实际应用中,鸽巢原理也可以推广到更高维度的情况,例如将 \(n\) 个球放入 \(m\) 个箱子中,每个球的颜色不同,每个箱子的容量相等,每个箱子内球的颜色不同,要求每个球都恰好放在一个箱子中。那么由于共有 \(nm\) 个位置,而有 \(n\) 个球,因此至少有一个箱子中有两个及以上的球。
以下是一个简单的求解鸽巢原理的 C++ 代码示例:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
该程序先输入了 n 个鸽子和 m 个鸟巢的数量,然后判断了鸽子的数量是否大于鸟巢的数量,若是,则无法将鸽子全部放入鸟巢中,程序输出一条错误信息;若否,则计算每个鸟巢中至少应该放几只鸽子,最后输出结果。注意,程序中使用了整数除法和取模运算来计算每个鸟巢中至少应该放几只鸽子。
4.2.5 二项式定理
二项式定理是一个关于 \((a+b)^n\) 的公式,其中 \(a,b\) 为实数,\(n\) 为正整数。它可以写成如下形式:
其中,\(\binom{n}{i}\) 表示从 \(n\) 个不同的元素中,取出 \(i\) 个元素的组合数,也称为二项式系数,可以用下面的公式计算:
二项式定理在组合数学、高中数学和大学数学中都有广泛的应用。
4.2.6 容斥原理
Text Only | |
---|---|
1 |
|
在组合数学中,容斥原理可以表述为:如果A1,A2,…,An是任意n个集合,则这些集合的并集的大小可以通过以下公式来计算:
|A1 ∪ A2 ∪ ... ∪ An| = Σ|Ai| - Σ|Ai ∩ Aj| + Σ|Ai ∩ Aj ∩ Ak| - ... +(-1)^(n-1)|A1 ∩ A2 ∩ ... ∩ An|
其中,Σ表示求和,|A|表示集合A的元素个数,∪表示集合的并集,∩表示集合的交集,(-1)^(n-1)表示(-1)的(n-1)次幂。
该公式的意义是,首先将所有集合的元素数量相加,得到这些集合的总元素数,然后依次减去它们的交集、并集、三者交集……直到最后的并集。
容斥原理常常被用于解决各种计数问题,如排列组合问题、随机事件计算等等。
下面是一个使用容斥原理计算两个集合交集大小的简单示例的 C++ 代码:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
输出:
Text Only | |
---|---|
1 2 |
|
4.2.7卡特兰数
卡特兰数是一类组合数学中的数列,其通项公式为: \(\(C_n=\frac{1}{n+1}\binom{2n}{n}=\frac{(2n)!}{(n+1)!n!}\)\) 卡特兰数在计数问题中有着广泛的应用,比如括号匹配问题、山峰与山谷问题、进出栈序列问题等。
以下是卡特兰数的一些性质:
- \(C_0=1\)
- \(C_n\) 的值增长速度非常快,近似于 \(4^n\)
- \(C_n\) 是整数,并且对于任意 \(n>0\),\(C_n\) 都是偶数
- \(C_n\) 是 \((n+1)\) 阶 Bell 数列的第 \(n\) 项
以下是卡特兰数的 C++ 动态规划代码实现:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
本页面的全部内容在 小熊老师 - 莆田青少年编程俱乐部 0594codes.cn 协议之条款下提供,附加条款亦可能应用