题解:洛谷 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\)。
-
算法选择:
- 枚举 + 排序:枚举所有满足 \(0 \le a \le b \le n\) 的分数对,用 \(\gcd(a,b)=1\) 筛选最简分数,按浮点值排序后输出
- 浮点比较:用
double存储分数值用于排序,分子分母用整数存储用于输出
-
关键步骤:
- 读入:\(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升序排列所有分数 - 输出:依次输出每个分数的分子和分母
-
时间/空间复杂度:
- 时间复杂度:\(O(n^2 \log n)\),枚举 \(O(n^2)\) 个分数,排序 \(O(n^2 \log n^2)\)
- 空间复杂度:\(O(n^2)\),存储所有最简分数
-
枚举排序的核心思想:
- 范围控制:\(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
浙公网安备 33010602011771号