3.2 最大公约数(GCD)&斐波那契数列 & 素数相关(机试高频数学考点)

最大公约数(Greatest Common Divisor,GCD)是机试中高频基础考点,核心是掌握欧几里得算法(辗转相除法),并能灵活应用到 "最简分数、分数化简、组合计数" 等变形题中。

一、核心知识点

1. 欧几里得算法(辗转相除法)

核心原理 :两个数的最大公约数 = 较小数 和 较大数 % 较小数 的最大公约数,直到其中一个数为 0,另一个数就是 GCD。递归实现(最简版)

cpp 复制代码
// 求a和b的最大公约数(a,b为正整数)
int gcd(int a, int b) {
    if (b == 0) return a;          // 终止条件:b=0时,a是GCD
    return gcd(b, a % b);          // 递归:gcd(a,b) = gcd(b, a%b)
}

迭代实现(避免递归栈溢出)

cpp 复制代码
int gcd(int a, int b) {
    while (b != 0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

2. 核心性质(机试常用)

  • gcd(a, b) = 1,则 a 和 b互质(无法约分,是最简分数的核心条件);
  • 分数化简:x/y = (x/gcd(x,y)) / (y/gcd(x,y))(如 12/30 = (12/6)/(30/6) = 2/5);
  • 多个数的 GCD:gcd(a,b,c) = gcd(gcd(a,b), c)(递推计算)。

二、经典例题 1:最简真分数计数

题目描述

输入 n 个正整数(n≤600,数≤1000),任取两个数作为分子 / 分母组成最简真分数(分子 < 分母且互质),求组合数。

  • 输入样例:7 → 3 5 7 9 11 13 15 → 输出 17;
  • 核心条件:① 分子 <分母(枚举时 i<j,避免重复);② gcd (分子,分母)=1(互质)。

完整 C 语言代码(注释版)

cpp 复制代码
#include <stdio.h>

// 欧几里得算法求最大公约数
int gcd(int a, int b) {
    if (b == 0) return a;
    return gcd(b, a % b);
}

int main() {
    int n;
    int nums[605]; // 存储输入的n个数(n≤600,留冗余)
    // 循环输入测试用例,直到文件结束
    while (scanf("%d", &n) != EOF) {
        // 输入n个正整数
        for (int i = 0; i < n; i++) {
            scanf("%d", &nums[i]);
        }
        
        int count = 0; // 统计最简真分数的组合数
        // 枚举所有i<j的组合(保证分子<分母,避免重复)
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                // 互质则计数+1
                if (gcd(nums[i], nums[j]) == 1) {
                    count++;
                }
            }
        }
        // 输出结果
        printf("%d\n", count);
    }
    return 0;
}

代码逻辑验证(输入样例)

输入数组:[3,5,7,9,11,13,15]

  • 枚举 i=0(3):j=1 (5, gcd=1)、j=2 (7, gcd=1)、j=3 (9, gcd=3)、j=4 (11, gcd=1)、j=5 (13, gcd=1)、j=6 (15, gcd=3) → 计数 + 4;
  • 枚举 i=1(5):j=2 (7,1)、j=3 (9,1)、j=4 (11,1)、j=5 (13,1)、j=6 (15,5) → 计数 + 4(累计 8);
  • 后续枚举累计最终为 17,和样例输出一致。

三、经典例题 2:分数化简(机试常见变形)

题目描述

输入分数 x/y(x、y 为正整数),将其化简为最简分数(分子分母互质)。

  • 示例:输入 12 30 → 输出 2/5;输入 18 24 → 输出 3/4。
完整 C 语言代码
cpp 复制代码
#include <stdio.h>

int gcd(int a, int b) {
    if (b == 0) return a;
    return gcd(b, a % b);
}

int main() {
    int x, y;
    // 输入分子x和分母y
    while (scanf("%d%d", &x, &y) != EOF) {
        int g = gcd(x, y); // 求最大公约数
        // 化简:分子分母分别除以GCD
        int simplified_x = x / g;
        int simplified_y = y / g;
        printf("%d/%d\n", simplified_x, simplified_y);
    }
    return 0;
}

边界处理

  • 若 y=0:需提示输入错误(分母不能为 0);
  • 若 x=0:化简结果为 0/1(0 和任何数的 GCD 是该数本身)。

补充容错版代码:

cpp 复制代码
#include <stdio.h>

int gcd(int a, int b) {
    if (b == 0) return a;
    return gcd(b, a % b);
}

int main() {
    int x, y;
    while (scanf("%d%d", &x, &y) != EOF) {
        if (y == 0) {
            printf("Input error! Denominator cannot be 0.\n");
            continue;
        }
        if (x == 0) {
            printf("0/1\n");
            continue;
        }
        int g = gcd(x, y);
        printf("%d/%d\n", x/g, y/g);
    }
    return 0;
}

四、GCD 其他常见变形考法

1. 最小公倍数(LCM)

  • 公式:lcm(a,b) = a*b / gcd(a,b)(先乘后除可能溢出,建议 a/gcd(a,b)*b);
  • 应用:如 "求两个数的最小公倍数""多个数的最小公倍数"。

代码实现:

cpp 复制代码
int lcm(int a, int b) {
    int g = gcd(a, b);
    return a / g * b; // 先除后乘避免溢出(如a=1e9, b=1e9时,a*b会溢出)
}

2. 多个数的 GCD

  • 递推计算:先算前两个数的 GCD,再和第三个数算 GCD,依此类推。

代码实现:

cpp 复制代码
// 求数组nums中n个数的最大公约数
int gcd_n(int nums[], int n) {
    int res = nums[0];
    for (int i = 1; i < n; i++) {
        res = gcd(res, nums[i]);
        if (res == 1) break; // 优化:GCD=1时无需继续计算
    }
    return res;
}

3. 统计区间内与 n 互质的数

  • 问题:求 1~m 中与 n 互质的数的个数;
  • 解法:遍历 1~m,判断 gcd (i,n)==1,计数即可。

总结

  1. GCD 核心:欧几里得算法 (递归 / 迭代均可),记住 gcd(a,b) = gcd(b,a%b),终止条件b=0
  2. 核心应用:
    • 最简真分数:枚举 i<j 的组合,判断gcd(nums[i],nums[j])==1
    • 分数化简:分子分母分别除以 GCD;
    • 最小公倍数:lcm(a,b) = a/gcd(a,b)*b
  3. 优化技巧:
    • 枚举组合时仅需 i<j(避免重复计算,如 3/5 和 5/3 算一个);
    • 计算 LCM 时先除后乘,避免数值溢出;
    • 多个数的 GCD 计算中,若中途结果为 1 可直接终止(1 和任何数的 GCD 都是 1)。

掌握这些核心知识点和变形,机试中所有 GCD 相关题目都能解决。

3.4 斐波那契数列

核心知识点

  1. 定义:F(1)=1,F(2)=1,F(n)=F(n-1)+F(n-2)(n≥3);
  2. 关键注意点
    • 数列增长极快:F (45)≈1.1e9(超出 int 范围),F (90)≈2.88e18(超出 long long 范围);
    • 若题目要求取模(如模 1e9+7),可边算边取模,甚至用矩阵快速幂计算超大项(如第 1e7 项);
    • 特殊变形:数列 a(1)=1,a(n+1)=1+1/a(n) 的通项为 a(n)=F(n+1)/F(n)

实用代码(带取模,避免溢出)

cpp 复制代码
#include <stdio.h>

// 计算斐波那契第n项,mod为模数(如1e9+7)
long long fib(int n, int mod) {
    if (n <= 2) return 1 % mod;
    long long a = 1, b = 1, res;
    for (int i = 3; i <= n; i++) {
        res = (a + b) % mod; // 边算边取模,避免溢出
        a = b;
        b = res;
    }
    return res;
}

int main() {
    int n;
    scanf("%d", &n);
    printf("F(%d) = %lld\n", n, fib(n, 1000000007)); // 示例模数
    return 0;
}

机试常见变形

  • 走台阶问题:一次走 1/2 步,走 n 级台阶的方法数 = 斐波那契第 n+1 项;
  • 兔子繁殖问题:经典斐波那契应用,和数列定义完全一致;
  • 分数通项问题:如 a(n)=1+1/a(n-1) 可转化为斐波那契比值。

3.5 素数判定(单个素数判断)

核心知识点

  1. 素数定义:大于 1 的自然数,除了 1 和自身无其他约数;
  2. 优化判定逻辑 :无需遍历到 x-1,只需遍历到 sqrt(x)(若 x 有大于 sqrt (x) 的约数,必然有一个小于 sqrt (x) 的约数);
  3. 边界处理:1 不是素数,2 是最小的素数,偶数(除 2 外)一定不是素数。

经典例题:找大于 n 的第一个素数

题目描述

输入整数 n(最大 10000),若 n 是素数则输出 n;否则输出大于 n 的第一个素数(如输入 14→输出 17)。

完整代码(优化版)
cpp 复制代码
#include <stdio.h>
#include <math.h>

// 判定x是否为素数(核心函数)
int isPrime(int x) {
    if (x <= 1) return 0;    // 1及以下不是素数
    if (x == 2) return 1;    // 2是素数
    if (x % 2 == 0) return 0;// 偶数(除2外)不是素数
    // 只遍历奇数,从3到sqrt(x),步长2(优化效率)
    for (int j = 3; j <= sqrt(x); j += 2) {
        if (x % j == 0) return 0;
    }
    return 1;
}

int main() {
    int n;
    scanf("%d", &n);
    // 从n开始遍历,找到第一个素数
    for (int i = n; ; i++) {
        if (isPrime(i)) {
            printf("%d\n", i);
            break;
        }
    }
    return 0;
}
代码优化点
  • 单独处理偶数:减少一半遍历次数;
  • 提取isPrime函数:代码复用性更高,逻辑更清晰;
  • 遍历步长设为 2:仅判断奇数,效率提升。

3.6 素数筛选(区间素数统计)

核心知识点

  1. 问题背景 :若需多次查询区间内素数个数,逐个判定效率低(O (n√n)),需用线性筛(欧拉筛) 预处理(O (n));
  2. 线性筛原理
    • 用数组标记非素数,遍历每个数,若未被标记则为素数;
    • 用当前素数乘以遍历数,标记乘积为非素数;
    • 若遍历数能被当前素数整除,停止标记(避免重复标记,保证线性复杂度)。

经典例题:统计区间 [a,b] 内的素数个数

题目描述

多组输入 a、b(2≤a,b≤1000),输出区间内素数个数(如输入 2 4→输出 2,输入 4 6→输出 1)。

完整 C 语言代码
cpp 复制代码
#include <stdio.h>
#include <string.h>

#define MAXN 1000005 // 筛到1e6,满足绝大多数机试需求

// prime数组:prime[0]存素数个数,prime[1..prime[0]]存素数,其余位置标记非素数(1=非素数,0=素数)
int prime[MAXN];

// 线性筛预处理素数
void getPrime() {
    memset(prime, 0, sizeof(prime)); // 初始化全为0(默认是素数)
    for (int i = 2; i < MAXN; i++) {
        if (!prime[i]) { // 未被标记,是素数
            prime[++prime[0]] = i;
        }
        // 用当前素数标记乘积为非素数
        for (int j = 1; j <= prime[0] && (long long)prime[j] * i < MAXN; j++) {
            prime[prime[j] * i] = 1; // 标记为非素数
            if (i % prime[j] == 0) { // 避免重复标记,终止循环
                break;
            }
        }
    }
}

int main() {
    getPrime(); // 预处理所有素数(只需执行一次)
    int a, b;
    while (scanf("%d%d", &a, &b) != EOF) {
        // 保证a ≤ b
        if (a > b) {
            int temp = a;
            a = b;
            b = temp;
        }
        int count = 0;
        // 遍历预处理的素数,统计区间内的数量
        for (int i = 1; i <= prime[0]; i++) {
            if (prime[i] >= a && prime[i] <= b) {
                count++;
            }
            if (prime[i] > b) { // 超出区间,提前终止
                break;
            }
        }
        printf("%d\n", count);
    }
    return 0;
}
代码关键说明
  • 纯 C 实现:替换bits/stdc++.hswap,用memset初始化,符合机试 C 语言环境;
  • 类型安全:(long long)prime[j] * i 避免乘法溢出;
  • 提前终止:遍历素数时超出 b 则 break,提升效率。

核心对比:素数判定 vs 素数筛选

方法 时间复杂度 适用场景
单个判定 O(√n) 仅判断 1-2 个数是否为素数
线性筛 O(n) 多次查询区间素数、大范围素数

总结

斐波那契数列

  1. 核心:边算边取模避免溢出,超大项用矩阵快速幂;
  2. 变形:走台阶、兔子繁殖、分数通项均需转化为斐波那契模型。

素数相关

  1. 单个素数判定:遍历到√x,提前处理偶数,效率更高;
  2. 区间素数统计:先线性筛预处理,再查询(机试高频考法);
  3. 线性筛关键:i % prime[j] == 0 时 break,保证线性复杂度。

掌握这些代码和思路,机试中所有斐波那契、素数相关题目都能解决,尤其是素数筛选是机试 "区间统计类" 题目的核心工具。

相关推荐
2301_776508721 小时前
C++中的职责链模式实战
开发语言·c++·算法
sqyno1sky2 小时前
C++中的空对象模式
开发语言·c++·算法
yunyun321232 小时前
动态库热加载技术
开发语言·c++·算法
88号技师2 小时前
2026年3月一区SCI-B样条曲线优化算法B-spline curves optimizer-附Matlab免费代码
开发语言·算法·数学建模·matlab·优化算法
dapeng28702 小时前
C++中的享元模式实战
开发语言·c++·算法
jing-ya2 小时前
day 60 图论part11
java·数据结构·算法·图论
沐雲小哥2 小时前
CenterPoint算法改进的tricks
算法
沐雲小哥2 小时前
Sparse4D算法的tricks
算法
沉鱼.442 小时前
树的题目集
数据结构·算法