题解:洛谷 P1458 顺序的分数

【题目来源】

洛谷:P1458 [USACO2.1] 顺序的分数 Ordered Fractions - 洛谷

【题目描述】

输入一个自然数 \(n\),对于一个最简分数 \(a/b\)(分子和分母互质的分数),满足 \(1≤b≤n,0≤a/b≤1\),请找出所有满足条件的分数。

这有一个例子,当 \(n=5\) 时,所有解为:

img/Pasted image 20260519125853.png

给定一个自然数 \(n\),请编程按分数值递增的顺序输出所有解。

注:

1、\(0\) 和任意自然数的最大公约数就是那个自然数。

2、互质指最大公约数等于1的两个自然数。

【输入】

单独的一行一个自然数 \(n\)

【输出】

每个分数单独占一行,按照大小次序排列

【输入样例】

5

【输出样例】

0/1
1/5
1/4
1/3
2/5
1/2
3/5
2/3
3/4
4/5
1/1

【核心思想】

  1. 问题分析:给定自然数 \(n\),要求输出所有满足 \(0 \le a \le b \le n\)\(\gcd(a,b)=1\) 的最简分数 \(a/b\),并按数值递增排列。该序列正是法里序列(Farey Sequence)\(F_n\)

  2. 算法选择

    • 枚举 + 排序:枚举所有满足 \(0 \le a \le b \le n\) 的分数对,用 \(\gcd(a,b)=1\) 筛选最简分数,按浮点值排序后输出
    • 浮点比较:用 double 存储分数值用于排序,分子分母用整数存储用于输出
  3. 关键步骤

    • 读入\(n\)
    • 初始化fra[0] = {0.0, 0, 1},表示 \(0/1\)
    • 双重枚举
      • 外层 \(i\)\(1\)\(n\)(分子)
      • 内层 \(j\)\(i\)\(n\)(分母,保证 \(a \le b\)
      • \(\gcd(i, j) = 1\):记录 fra[mark] = {1.0*i/j, i, j}mark++
    • 排序:按 res 升序排列所有分数
    • 输出:依次输出每个分数的分子和分母
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n^2 \log n)\),枚举 \(O(n^2)\) 个分数,排序 \(O(n^2 \log n^2)\)
    • 空间复杂度:\(O(n^2)\),存储所有最简分数
  5. 枚举排序的核心思想

    • 范围控制\(a \le b \le n\)\(a/b \le 1\),内层从 \(i\) 开始枚举避免重复和超界
    • 互质筛选\(\gcd(a,b)=1\) 保证分数为最简形式,避免重复输出等价分数
    • 浮点辅助排序:用 double 存储数值用于比较排序,输出时用原始整数分子分母保证精度
    • 法里序列性质:该序列具有相邻项满足 \(bc-ad=1\) 的性质,但本题数据范围小,直接枚举排序即可
    • 适用于小规模分数枚举、互质对统计、有序序列生成等问题

【解题思路】

【算法标签】

普及- #约数

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n, mark;
struct node {  // 定义分数结构体
    double res;  // 分数结果,注意是double类型
    int a, b;  // 分子和分母
}fra[25600];  // 按照160*160的最大范围开辟结构体数组
int gcd(int a, int b)  // 最大公约数模板(背就完了!)
{
    int r = a % b;
    while (r!=0) {
        a = b;
        b = r;
        r = a % b;
    }
    return b;
}
bool cmp (node x, node y)  // 比较函数,按照res从小到大排序
{
    return x.res<y.res;
}
int main()
{
    cin >> n;  // 输入n
    fra[0].res = 0;  // 数组第1个res为0
    fra[0].a = 0, fra[0].b = 1;  // 分子为0,分母为1
    mark=1;  // 定义计数器
    for (int i=1; i<=n; i++) {  // 从1遍历至n
        for (int j=i; j<=n; j++) {  // 从i遍历至n
            if (gcd(i,j)==1) {  // 如果分子和分母的最大公约数为1
                fra[mark].res = 1.0*i/j;  // 记录分数结果
                fra[mark].a = i, fra[mark].b = j;  // 以及分子和分母
                mark++;  // 计数器自增1
            }
        }
    }
    sort(fra, fra+mark, cmp);  // 对结构体数组按照res从小到大方式排序
    for (int i=0; i<mark; i++) {  // 依次输出mark-1(因为存完最后一个mark仍自增了1)个分子和分母
        cout << fra[i].a << "/" << fra[i].b << endl;
    }
    return 0;
}

【运行结果】

5
0/1
1/5
1/4
1/3
2/5
1/2
3/5
2/3
3/4
4/5
1/1
posted @ 2026-08-02 08:38  团爸讲算法  阅读(12)  评论(0)    收藏  举报