0x08 目录
7.1 同余
同余 是用来解决什么问题的呢 ?
当 问题是一个 很大的运算,结果 也很大。根本 没办法 用 基本类型取存储。但是呢,它 却告诉你 这个结果是要 对 m 取余的。那么 我们就完全 不需要写 大数乘法
这样臃肿的 代码。我们可以 采用 同余 去解决这个问题。
7.1.1 同余定理
- (a + b) mod n = ((a mod n) + (b mod n)) mod n
- (a - b) mod n = ((a mod n) - (b mod n)) mod n
- (a × b) mod n = ((a mod n) × (b mod n)) mod n
除法 取余的话,就要 用 逆元
。
7.1.2 例题举例
- 双阶乘 的最后五位数
双阶乘的定义是:提供给你个 n,那么 n!! 就是 从 1 到 n 跟 n 有共同就性质的数的 乘积结果。
2021!! = 2021 * 2019 * 2017 …… * 1
那么 问 2021!! 的 最后五位数是什么 ?
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
// 此题 考的就是 大数乘法,但实际我们 可以用 java 的bigInteger 来解决这个问题
// 但是如果学过 一点儿 数论的话,其实这道题 很好解决。
// 无论怎么相乘,你的 最后那几位 肯定 跟前面没啥关系吧。所以每次相乘的结果 取余数 再进行相乘 即可。
int main15(void) {
int ans = 2021;
for (int i = 2019; i >= 1; i -= 2) {
ans = (ans * i) % 100000;
}
cout << ans << endl;
return 0;
}
答案是:59375
这道题 就利用了 同余,首先你要知道 同余这个结论。然后再去 分析这道题,就会感到 很简单了。
ans % 100000 = ans 的后五位数,这是 毋庸置疑的。 然后 我们又 发现 它是 连乘的。则 根据 同余定理,每次 (a * b) % mod 然后 相乘 再 % mod 就 跟 ans % 100000 是等价的。7.2 GCD 最大公因数
求最大公约数、最小公倍数、素数、矩阵的加减乘除、闰年和平年、快速幂 、数列 …… 等等 这些 都是 最基本的 数学问题。是 我们在解决 一系列 算法题时 的基础数学常识。
7.2.1 辗转相除法
辗转相除法 时 欧几里得 发明出来 求解 两数 的最大公因数的数学方法。
其操作步骤是:a % b = c 然后 让 a = b,b = c 一直这样进行下去,直到 b == 0 的时候,我们 才会停止下来。然后 此时 a 的值 其实就是 两数的 最大公因数。
如下算法:有两数 a 和 b,前提是 a 必须 大于 b !!!
- 递归写法
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
很不建议 用 递归的写法,因为 递归 很容易 栈溢出。
- 迭代写法
int main(){
int a, b;
cin >> a >> b;
if (b > a) {
int temp = a;
a = b;
b = temp;
}
while (b != 0) {
int temp = a;
a = b;
b = temp % b;
}
cout << a << endl;
return 0;
}
这种 正常的 迭代 写法,反而 是最好的。我们只需要 把 这种方法 封装到 一个 函数里就行了。
7.2.2 更相减损法
更相减损法
第一步:任意给定两个正整数;判断它们是否都是偶数。若是,则用2约简;若不是则执行第二步。
第二步:以较大的数减较小的数,接着把所得的差与较小的数比较,并以大数减小数。继续这个操作,直到所得的减数和差相等为止。
则第一步中约掉的若干个2与第二步中等数的乘积就是所求的最大公约数。
其中所说的“等数”,就是最大公约数。求“等数”的办法是“更相减损”法。所以更相减损法也叫等值算法。
从上述这个过程来看,就是个模拟。我们可以用 代码写出来!
#include <iostream>
using namespace std;
int main(void) {
int a, b;
cin >> a >> b;
if (b > a) {
int temp = a;
a = b;
b = temp;
}
while (a % 2 == 0 && b % 2 == 0) {
a /= 2;
b /= 2;
}
int val = a - b;
while (val != b) {
if (b > val) {
a = b;
b = val;
}
else {
a = val;
}
val = a - b;
}
cout << val << endl;
return 0;
}
7.2.3 最小公倍数
即 最小公倍数 = a * b / 最大公因数
7.3 扩展 GCD · 贝祖定理
GCD 求最大公因数的 这个方法,其实 还可以运用 在 求解 ax + by = c
贝祖定理:对任何整数 a、b 和 它们的最大公因数 c,关于 未知数 x 和 y 的线性不定方程(称为贝祖等式):若 a、b 是 整数,且 gcd(a,b) = c,那么 对于 任意 的 整数 x,y,ax+by 都一定是 d 的倍数,特别地,一定存在 整数 x,y 使得 ax+by = c 成立。
也就是说 我们提供了 a、b 两个 数,那么肯定会有一个 c 是 gcd(a,b) 的倍数。并且 也可以 求出 整数 x,y 。
你还需要知道的是:取余算法是这样的式子
那么 我们 根据 欧几里得算法 和 贝祖定理 就可以推导出 下面的 等式。
因为 x 和 y 的 系数 发生了变化,所以 当前的 x 和 y 肯定 也不是我们要求的 x 和 y 了,我们就记作它们为 x’ 和 y’
x = y'
y = x' - (a/b) * y' 在 c 语言里 a/b 取的就是 小于 a/b 的最大值整数。
我们已经得到了这样的等式,那么 这里的 x’ 和 y’ 又要怎么 去 求呢 ??
其实 很简单,由欧几里得 gcd 可知,a = b,b = a % b 也就是说 系数是会不断的 变化的,但 变化到 a = 最大公因数 b = 0 的时候 就会停下来,而且 无论是 哪两个 a和b 只要是 ax+by = gcd(a,b) * k 这样的式子的话,就肯定 满足。所以 这可以是 求解 第一个 x’ 和 y’ 的 通用方法。
那么我们来模拟 一下 整体的过程。
int ex_gcd(int a,int b,int &x,int &y){
if(b == 0){
x = 1;// 这个就是 x'
y = 0;// 这个就是 y'
return a; // 返回最大公因数
}
int ans = ex_gcd(b,a%b,x,y);// 肯定会获取到 x' 和 y'
int temp = x;// 把x' 存起来
x = y;// 即 x = y'
y = temp - (a/b)*x;
return ans;
}
那么 有人的会问 这是在干嘛呢? 其实 我自己也不知道 是在干嘛。
因为 确实 是 还没有刷到 利用 贝祖定理 的 题目。平常 遇到的 也就是 gcd() 最大公因数 和 最小公倍数。而且 遇到的还很少。所以这个 只作为了解吧。