0x08 扩展GCD和同余


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 例题举例

  1. 双阶乘 的最后五位数

双阶乘的定义是:提供给你个 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 !!!

  1. 递归写法
int gcd(int a, int b) {
	return b ? gcd(b, a % b) : a;
}

很不建议 用 递归的写法,因为 递归 很容易 栈溢出。

  1. 迭代写法
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() 最大公因数 和 最小公倍数。而且 遇到的还很少。所以这个 只作为了解吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值