题解:洛谷 P1458 顺序的分数
【题目来源】
洛谷:P1458 [USACO2.1] 顺序的分数 Ordered Fractions - 洛谷
【题目描述】
输入一个自然数 \(n\),对于一个最简分数 \(a/b\)(分子和分母互质的分数),满足 \(1≤b≤n,0≤a/b≤1\),请找出所有满足条件的分数。
这有一个例子,当 \(n=5\) 时,所有解为:

给定一个自然数 \(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
【核心思想】
-
问题分析:给定自然数 \(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\)。
-
算法选择:
- 法里序列递推:利用相邻两项的中项性质直接按顺序生成下一个分数,无需枚举再排序。
- 整数递推公式:已知相邻两项 \(a/b < c/d\),下一项为 \(\big(kc-a\big)/\big(kd-b\big)\),其中 \(k = \lfloor (n+b)/d \rfloor\)。
-
关键步骤:
- 初始化:设前两项为 \(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\) 会在循环中自然生成并输出。
-
时间/空间复杂度:
- 时间复杂度:\(O(|F_n|)\),即 \(O(n^2)\),因为法里序列长度约为 \(3n^2/\pi^2\)。
- 空间复杂度:\(O(1)\),仅需维护两对相邻分数的分子分母。
-
法里序列的核心思想:
- 相邻互素性:若 \(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
浙公网安备 33010602011771号