杂谈 2:论编程中的简单数学
杂谈 2:论编程中的简单数学
质数
因数只有 和数字本身的数称为质数。
素数计数函数:小于或等于 的素数的个数,用 表示。随着 的增大,有这样的近似结果:。
素性测试:素性测试(Primality test)是一类在不对给定数字进行素数分解(prime factorization)的情况下,测试其是否为素数的算法。
那我们怎么判断一个数是不是质数呢?我们可以枚举 ,对于每个 ,如果 ,那么就不是质数。当然, 时 一定不是质数。
代码:
bool Prime(ll x) {
if (x < 2) {
return 0;
}
for (ll i = 2; i <= x; ++ i) {
if (!(x % i)) {
return 0;
}
}
return 1;
}
可是,这样求解的时间复杂度是 , 太大直接爆炸。我们发现如果 是 的约数,那么 也是 的约数(称 为 的约数对)。所以不必重复枚举 个,我们只要枚举最小的那个即可。由推理可得,所有 的约数对的最小数均 。所以只需枚举 即可。
代码:
bool Prime(ll x) {
if (x < 2) {
return 0;
}
for (ll i = 2; i * i <= x; ++ i) { // i * i <= x 等价于 i <= √x
if (!(x % i)) {
return 0;
}
}
return 1;
}
那怎么一次性求出 的所有数是不是质数呢?如果把每个数都检验一遍,那么时间复杂度为 ,大概能撑到 。下面介绍一种 的算法:
找到一个质数后,就将它的倍数标记为合数,也就是把它的倍数筛掉,如果一个数没有被比它小的质数筛掉,那它就是质数。直到把 的质数全部筛完,程序结束。时间复杂度为 。
代码:
const int kMaxN = 1e6 + 10; // 筛掉 1 ~ 10^6+10 的质数
bool isp[kMaxN + 1]; // 多开 1 个保险
bool InitPrime(ll x) {
for (int i = 2; i * i <= kMaxN; ++ i) {
if (!isp[i]) { // 如果 i 是质数
for (int j = i * i; j <= kMaxN; j += i) { // 标记
isp[j] = 1;
}
}
}
}
最大公约数
最大公约数即为 Greatest Common Divisor,常缩写为 gcd。一组整数的最大公约数,是指所有公约数里面最大的一个。
个数的最大公约数:
考虑枚举 ,找到最大的 有 ,则 是 的值。
代码:
ll GCD(ll a, ll b) {
ll ans = 0;
for (ll i = 1; i <= min(a, b); ++ i) {
if (!(i % a) && !(i % b)) {
ans = i;
}
}
return ans;
}
但是,这个算法的时间复杂度是 , 和 太大就会超时。设 ,我们发现如果 是 的约数,那么 就是二者的最大公约数。下面讨论不能整除的情况,即 ()。我们可以得到 。
代码:
ll GCD(ll a, ll b) {
if (!b) {
return a;
}
return GCD(b, a % b);
}
但大整数()取模的时间复杂度较高,而加减法时间复杂度较低。针对大整数,我们可以用更相减损术。设 ,有:
因为当 时,,有 ,否则可以证明 的所有公因数都是 的公因数。
代码:
BigInt GCD(BigInt a, BigInt b) {
int cnt1 = 0, cnt2 = 0;
for (; !Mod(a, 2); ++ cnt1) { // Mod 为高精 a % b 函数
RMove(a, 1); // a >>= 1 的高精版
}
for (; !Mod(b, 2); ++ cnt1) { // Mod 为高精 a % b 函数
RMove(b, 1); // b >>= 1 的高精版
}
for (; ; ) {
for (; !Mod(a, 2), RMove(a, 1));
for (; !Mod(b, 2), RMove(b, 1));
if (Same(a, b)) { // a == b 的高精版
break;
}
if (a < b) {
Swap(a, b); // 高精 swap(a, b)
}
Diff(a, b); // a -= b 高精版
}
return LMove(a, min(cnt1, cnt2)); // LMove 为 a <<= b 的高精版
}
多个数的最大公约数:
显然答案一定是每个数的约数,那么也一定是每相邻两个数的约数。
代码:
ll GCD(ll a, ll b) {
if (!b) {
return a;
}
return GCD(b, a % b);
}
ll MGCD() {
int _gcd = a[1];
for (int i = 2; i <= n; ++ i) {
_gcd = GCD(_gcd, a[i]);
}
return _gcd;
}
快速幂
我们知道 乘上 ,等于 ,所以有 。因此,设 表示 的 次方,我们可以这样算:
代码:
ll Qkpow(ll a, ll b) {
if (!b) {
return 1;
}
if (b == 1) {
return a;
}
int tmp = Qkpow(a, (b >> 1)); // b >> 1 相当于 b / 2
return tmp * tmp * ((b & 1)? a : 1); // b & 1 相当于 b % 2
}
但是,我们还可以使用二进制的方式进行运算。其实跟前面差不多,但是省去了递归步骤。
代码:
// 无模数版
ll Qkpow(ll b, ll p) {
ll ans = 1;
for (; p; p >>= 1) { // p >>= 1 相当于 p /= 2
if (p & 1) {
ans = ans * b;
}
b = b * b;
}
return ans;
}
// 有模数版
ll Qkpow(ll b, ll p, ll mod) {
ll ans = 1;
for (; p; p >>= 1) { // p >>= 1 相当于 p /= 2
if (p & 1) {
ans = ans * b % mod;
}
b = b * b % mod;
}
return ans;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效