题解:洛谷 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\),其核心性质是:序列中任意相邻两项 \(a/b\)\(c/d\) 满足 \(bc-ad=1\)

  2. 算法选择

    • 法里序列递推:利用相邻两项的中项性质直接按顺序生成下一个分数,无需枚举再排序。
    • 整数递推公式:已知相邻两项 \(a/b < c/d\),下一项为 \(\big(kc-a\big)/\big(kd-b\big)\),其中 \(k = \lfloor (n+b)/d \rfloor\)
  3. 关键步骤

    • 初始化:设前两项为 \(a/b = 0/1\)\(c/d = 1/n\)
    • 顺序生成(当 \(c \le n\) 时循环):
      • 计算 \(k = \lfloor (n+b)/d \rfloor\)
      • 下一项 \(e/f = (kc-a)/(kd-b)\)
      • 输出当前项 \(c/d\)
      • 滑动窗口:令 \(a/b \leftarrow c/d\)\(c/d \leftarrow e/f\)
    • 边界处理:首项 \(0/1\) 在循环前直接输出,末项 \(1/1\) 会在循环中自然生成并输出。
  4. 时间/空间复杂度

    • 时间复杂度:\(O(|F_n|)\),即 \(O(n^2)\),因为法里序列长度约为 \(3n^2/\pi^2\)
    • 空间复杂度:\(O(1)\),仅需维护两对相邻分数的分子分母。
  5. 法里序列的核心思想

    • 相邻互素性:若 \(a/b\)\(c/d\)\(F_n\) 中相邻,则 \(bc-ad=1\),保证两数之间无其他分母 \(\le n\) 的分数。
    • 中项跳表:递推公式 \(e/f=(kc-a)/(kd-b)\) 实质是用最大允许倍数 \(k\) 控制新分母不超过 \(n\),从而直接“跳”到下一个最简分数。
    • 避免浮点比较:全程使用整数运算,规避浮点精度问题,结果严格有序。
    • 在线性空间内顺序输出:不需要存储所有分数再排序,生成一个输出一个。
    • 适用于按顺序枚举区间 \([0,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  团爸讲算法  阅读(3)  评论(0)    收藏  举报