首先,Farey 数列 \(F_n\) 表示分母不超过 \(n\) 的所有既约真分数按大小顺序排列的集合,形式化来说

\[F_n = \left\{\frac{p}{q} \bigg\vert 0 < p < q \le n, \, \gcd(p, q) = 1\right\}
\]

这个数列的渐进大小为 \(\sum\limits_{i = 1}^{n} \varphi(n) \sim \frac{3}{\pi^2}n^2 + o(n \log n)\)。

关于 Farey 数列,有两个经典结论:

  • 若 \(\frac{a}{b}\) 和 \(\frac{c}{d}\) 是 \(F_n\) 中的两个连续元素,那么 \(\frac{a + c}{b + d}\) 也是一个合法的 Faray 数。

  • 若 \(\frac{a}{b}\) 和 \(\frac{c}{d}\) 是 \(F_n\) 中的两个连续元素,那么它们的下一个元素 \(\frac{p}{q}\) 满足

    \[\begin{cases}p = \left\lfloor\frac{n + b}{d}\right\rfloor c - a \\ q = \left\lfloor\frac{n + b}{d}\right\rfloor d - b\end{cases}
    \]

我们只对上述定理二进行证明,对 \(n\) 阶 Farey 数列,相邻两项 \(\frac{a}{b} < \frac{c}{d}\) 满足 \(bc - ad = 1\),根据相邻两项 \(\frac{a}{b}, \, \frac{c}{d}\) 均在 Stern-Brocot 树上,我们有下一项 \(\frac{p}{q}\) 满足 \(\frac{c}{d} = \frac{a + p}{b + q}\),也就是 \((p + a)d = (q + b)c\),因此存在 \(k\) 使得

\[\begin{cases} kc = a + p \\ kd = b + q \end{cases}
\]

为了使 \(p, \, q\) 精度高,那么 \(k\) 的值应该尽可能大,所以对上界 \(n\) 来说,\(p, \, q\) 由下式定义:

\[\begin{cases}p = kc - a \le n \\ q = kd - b \le n\end{cases}
\]

显然由真分数定义知 \(q > p\),所以最大 \(k = \left\lfloor\frac{n + b}{d}\right\rfloor\),因此

\[\begin{cases}p = \left\lfloor\frac{n + b}{d}\right\rfloor c - a \\ q = \left\lfloor\frac{n + b}{d}\right\rfloor d - b\end{cases}
\]

生成这个数列有两种计算方式,一是种基于第一个定理的 Stern-Brocot 树,这个很好实现。

int build(int a, int b, int c, int d, int n) {
if (b + d > n) return 0;
return 1 + build(a, b, a + c, b + d, n) + build(a + c, b + d, c, d, n);
}

另一种是基于第二个定理的递推,也比较好写。

PII nxt_fraction(PII fac1, PII fac2) {
auto [a, b] = fac1;
auto [c, d] = fac2;
return {(n + b) / d * c - a, (n + b) / d * d - b};
}

很显然,他们都是 \(O(n^2)\) 的时间复杂度进行 Faray 数列特定元素的计算。

为了方便我们解决母问题,我们给出这样两个问题:

给定 \(n, \, k\),求 \(F_n\) 的第 \(k\) 项。

给定 \(n\) 和既约真分数 \(\frac{p}{q}(p \le n)\),判断其在 \(F_n\) 中的排名。

我们先考虑第一个问题,我们发现第一个问题可以通过如下步骤进行:

  • 二分 \(\frac{j}{n}\) 中的 \(j\),计算 \(\text{rank}(\frac{j}{n})\)

    • 记 \(r = \text{rank}(\frac{j}{n})\),如果 \(r < k\),向上搜索,否则向下搜索。
  • 找到一个区间 \(\left[\frac{j}{n}, \, \frac{j + 1}{n}\right)\) 使得目标分数在这个范围以内

  • 统计这个区间内的分数,找到合法的分数使得其排名为所给 \(k\)

    • 注意到这个区间内不同分母的分数至多一个,因为 \(\frac{1}{n}\) 是最小间距了

    • 对于一个分母为 \(q\) 的分数,唯一可能的分数的分母只能是 \(\left\lfloor\frac{(j + 1)q - 1}{n}\right\rfloor\)

  • 找到严格大于 \(\frac{j}{n}\) 的最小分数 \(\frac{p}{q}\),利用结论二进行递推,直到给定分数为 \(k\) 排名

我们可以看出,问题一可以转化为问题二实现,因此我们现在来考虑问题二如何完成。

更平凡的,问题二可以规约为如下问题:

给定实数 \(x\),求分母 \(q \le n\) 的既约真分数 \(\frac{p}{q} \le x\) 的个数。

记 \(A_q\) 表示,以 \(q\) 为分母满足上述条件的既约真分数的个数,显然的,可以表示原问题为求这样一个集合:

\[S = \left\{\frac{p}{q} \bigg\vert \frac{p}{q} \le x, \, q \le n, \, \gcd(p, \, q) = 1\right\}
\]

也就是求 \(\sum\limits_{i = 1}^{n}A_i = |S|\),而我们知道

\[\left\lfloor x \cdot q \right\rfloor = \sum\limits_{d \mid q}A_d
\]

这告诉我们

\[A_q = \left\lfloor x \cdot q \right\rfloor - \sum\limits_{d \mid q, \, d < q}A_d
\]

按顺序从小到大递推即可,每次计算复杂度 \(O(n\log{n})\),那么问题一的复杂度就是 \(O(n\log^2{n})\),有没有更优的做法呢?

考虑 \(A_q\) 的本质形态,利用莫比乌斯反演,我们可以得到

\[A_q = \sum\limits_{i = 1}^{\lfloor x \cdot q \rfloor}[\gcd(i, q) = 1] = \sum\limits_{i = 1}^{\lfloor x \cdot q \rfloor}\sum\limits_{d \mid \gcd(i, q)}\mu(d)
\]

如果我们对所有 \(A_q\) 求和,那么就有

\[\begin{aligned}
|S| &= \sum_{i = 1}^{n}A_i \\
&= \sum_{i = 1}^{n}\sum_{j = 1}^{\lfloor x \cdot i \rfloor}[\gcd(i, \, j) = 1] \\
&= \sum_{i = 1}^{n}\sum_{j = 1}^{\lfloor x \cdot i \rfloor}\sum_{d \mid \gcd(i, j)}\mu(d) \\
&= \sum_{d \mid i, \, d \mid j}\sum_{i = 1}^{n}\sum_{j = 1}^{\lfloor x \cdot i \rfloor}\mu(d) \\
&= \sum_{d = 1}^{n}\sum_{i = 1}^{\left\lfloor\frac{n}{d}\right\rfloor}\sum_{j = 1}^{\lfloor x \cdot i \rfloor}\mu(d) \\
&= \sum_{d = 1}^{n}\mu(d)\sum_{i = 1}^{\left\lfloor\frac{n}{d}\right\rfloor}\lfloor x \cdot i \rfloor
\end{aligned}\]

考虑设 \(S(n) = \sum\limits_{i = 1}^{n}\lfloor x \cdot i \rfloor\),那么我们相当于要求的东西也就是

\[\sum_{d = 1}^{n}\mu(d)S\left(\left\lfloor\frac{n}{d}\right\rfloor\right)
\]

对 \(\lfloor x \cdot i \rfloor\) 求前缀和,预处理莫比乌斯函数,我们可以在 \(O(n)\) 时间复杂度解决这个问题。

如此,我们在解决 Farey 数列问题上我们有:

  • 预处理:\(O(n)\)

  • 给 \(\text{rank}\) 求分数:\(O(n\log{n})\)

  • 给分数求 \(\text{rank}\):\(O(n)\)

  • 求 \(F_n\) 中比某实数小的数的个数 \(O(n)\)

  • 正实数的有理逼近 \(O(n\log{n})\)

我们给出一份模版代码供参考

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define PII pair<ll, ll>
const int N = 2e6 + 10;
ll n;
ll primes[N], st[N], mu[N], cnt; // precalculate Mu
void init_mu() {
mu[1] = 1;
for (int i = 2; i < N; i ++ ) {
if (!st[i]) primes[cnt ++ ] = i, mu[i] = -1;
for (int j = 0; i * primes[j] < N; j ++ ) {
st[i * primes[j]] = 1;
if (i % primes[j] == 0) break;
mu[i * primes[j]] = -mu[i];
}
}
} // O(nlogn) calculate next one by one
PII nxt_fraction(PII fac) {
ll l = 0, r = n;
while (l < r) {
ll mid = l + r + 1 >> 1;
if (mid * fac.second <= n * fac.first) l = mid;
else r = mid - 1;
}
PII res = {r + 1, n};
for (int i = 1; i <= n; i ++ ) {
PII fac_i = {((r + 1) * i - 1) / n, i};
if (fac_i.first * fac.second <= fac_i.second * fac.first) continue;
if (fac_i.first * res.second < fac_i.second * res.first) res = fac_i;
}
ll d = __gcd(res.first, res.second);
return {res.first / d, res.second / d};
} // O(1) calculate next one by two nearly
PII nxt_fraction(PII fac1, PII fac2) {
auto [a, b] = fac1;
auto [c, d] = fac2;
return {(n + b) / d * c - a, (n + b) / d * d - b};
} ll fraction_to_rank(PII fac) {
static ll A[N];
for (int i = 1; i <= n; i ++ ) A[i] = fac.first * i / fac.second + A[i - 1];
ll res = 0;
for (int i = 1; i <= n; i ++ ) res += mu[i] * A[n / i];
return res;
} PII rank_to_fraction(ll k) {
ll l = 0, r = n;
while (l < r) {
ll mid = l + r + 1 >> 1;
if (fraction_to_rank({mid, n}) <= k) l = mid;
else r = mid - 1;
}
k -= fraction_to_rank({r, n});
PII a = {r / __gcd(r, n), n / __gcd(r, n)}, b = nxt_fraction(a);
if (!k) return a;
while (k -- ) a = nxt_fraction(a, b), swap(a, b);
return a;
} void solve() {
cin >> n;
PII now = {1, n};
while (now.first != now.second) {
ll rk = fraction_to_rank(now);
PII fac = rank_to_fraction(rk);
cout << rk << " : " << fac.first << " " << fac.second << " | " << now.first << " " << now.second << "\n";
now = nxt_fraction(now);
}
} int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
init_mu();
int T = 1;
// cin >> T;
while (T -- ) solve();
return 0;
}

Expansion

容易发现,我们实际问题中很难遇到 \(x\) 是无理数的情况,那么我们很容易想到,不妨设 \(x = \frac{p}{q}\),有

\[\sum_{d = 1}^{n}\mu(d)\sum_{i = 1}^{\left\lfloor\frac{n}{d}\right\rfloor}\lfloor x \cdot i \rfloor = \sum_{d = 1}^{n}\mu(d)\sum_{i = 1}^{\left\lfloor\frac{n}{d}\right\rfloor}\left\lfloor \frac{pi}{q} \right\rfloor
\]

容易看出,后者是我们类欧几里得算法的标准形式,利用数论分块,可以做到 \(O(\sqrt{n}\log^2{n})\) 每个询问。

更进一步,你可以使用杜教筛对前面的莫比乌斯函数求和,预处理时间复杂度降为 \(O(n^{\frac{2}{3}})\)。

如果你仍然对此法感觉不优,你可以利用狄利克雷前缀和分块处理,总体时间复杂度可以达到惊人的 \(O(n^{\frac{2}{3}} + \sqrt{n}\log^{\frac{3}{2}}{n})\)。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define PII pair<ll, ll>
const int N = 1e5;
ll n, k;
ll primes[N], st[N], mu[N], cnt;
unordered_map<ll, ll> summu; // precalculate Mu
void init_mu() {
mu[1] = 1;
for (int i = 2; i < N; i ++ ) {
if (!st[i]) primes[cnt ++ ] = i, mu[i] = -1;
for (int j = 0; i * primes[j] < N; j ++ ) {
st[i * primes[j]] = 1;
if (i % primes[j] == 0) break;
mu[i * primes[j]] = -mu[i];
}
}
for (int i = 2; i < N; i ++ ) mu[i] += mu[i - 1];
} ll get_mu(ll n) {
if (n < N) return mu[n];
if (summu.count(n)) return summu[n];
ll res = 1;
for (ll l = 2, r; l <= n; l = r + 1) {
r = n / (n / l);
res -= (r - l + 1) * get_mu(n / l);
}
return summu[n] = res;
} ll f(ll a, ll b, ll c, ll n) {
if (!a) return b / c * (n + 1);
if (a < c && b < c) {
ll m = (a * n + b) / c;
if (!m) return 0;
return n * m - f(c, c - b - 1, a, m - 1);
}
return f(a % c, b % c, c, n) + (n + 1) * n / 2 * (a / c) + (n + 1) * (b / c);
} // O(n + logn) calculate next one by one
PII nxt_fraction(ll n, PII fac) {
ll l = 0, r = n;
while (l < r) {
ll mid = l + r + 1 >> 1;
if (mid * fac.second <= n * fac.first) l = mid;
else r = mid - 1;
}
PII res = {r + 1, n};
for (int i = 1; i <= n; i ++ ) {
PII fac_i = {((r + 1) * i - 1) / n, i};
if (fac_i.first * fac.second <= fac_i.second * fac.first) continue;
if (fac_i.first * res.second < fac_i.second * res.first) res = fac_i;
}
ll d = __gcd(res.first, res.second);
return {res.first / d, res.second / d};
} // O(1) calculate next one by two nearly
PII nxt_fraction(ll n, PII fac1, PII fac2) {
auto [a, b] = fac1;
auto [c, d] = fac2;
return {(n + b) / d * c - a, (n + b) / d * d - b};
} // O(sqrt(n)logn) calculate fraction's rank
ll fraction_to_rank(ll n, PII fac) {
ll res = 0;
for (ll l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
res += (get_mu(r) - get_mu(l - 1)) * f(fac.first, 0, fac.second, n / l);
}
return res;
} // O(sqrt(n)log(n)^2) calculate the fraction of k-th rank
PII rank_to_fraction(ll n, ll k) {
ll l = 0, r = n;
while (l < r) {
ll mid = l + r + 1 >> 1;
if (fraction_to_rank(n, {mid, n}) <= k) l = mid;
else r = mid - 1;
}
k -= fraction_to_rank(n, {r, n});
PII a = {r / __gcd(r, n), n / __gcd(r, n)}, b = nxt_fraction(n, a);
if (!k) return a;
while (k -- ) a = nxt_fraction(n, a, b), swap(a, b);
return a;
} void solve() {
cin >> n;
PII now = {1, n};
while (now.first != now.second) {
ll rk = fraction_to_rank(n, now);
PII fac = rank_to_fraction(n, rk);
cout << rk << " : " << fac.first << " " << fac.second << " | " << now.first << " " << now.second << "\n";
now = nxt_fraction(n, now);
}
} int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
init_mu();
int T = 1;
// cin >> T;
while (T -- ) solve();
return 0;
}

恰好这里有一个练手题,试试看。

Faray 数列问题的更多相关文章

  1. C#求斐波那契数列第30项的值(递归和非递归)

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  2. BZOJ1500[NOI2005]维修数列

    Description Input 输入的第1 行包含两个数N 和M(M ≤20 000),N 表示初始时数列中数的个数,M表示要进行的操作数目.第2行包含N个数字,描述初始时的数列.以下M行,每行一 ...

  3. PAT 1049. 数列的片段和(20)

    给定一个正数数列,我们可以从中截取任意的连续的几个数,称为片段.例如,给定数列{0.1, 0.2, 0.3, 0.4},我们有(0.1) (0.1, 0.2) (0.1, 0.2, 0.3) (0.1 ...

  4. 斐波拉契数列加强版——时间复杂度O(1),空间复杂度O(1)

    对于斐波拉契经典问题,我们都非常熟悉,通过递推公式F(n) = F(n - ) + F(n - ),我们可以在线性时间内求出第n项F(n),现在考虑斐波拉契的加强版,我们要求的项数n的范围为int范围 ...

  5. fibonacci数列(五种)

    自己没动脑子,大部分内容转自:http://www.jb51.net/article/37286.htm 斐波拉契数列,看起来好像谁都会写,不过它写的方式却有好多种,不管用不用的上,先留下来再说. 1 ...

  6. js中的斐波那契数列法

    //斐波那契数列:1,2,3,5,8,13…… //从第3个起的第n个等于前两个之和 //解法1: var n1 = 1,n2 = 2; for(var i=3;i<101;i++){ var ...

  7. 洛谷 P1182 数列分段Section II Label:贪心

    题目描述 对于给定的一个长度为N的正整数数列A[i],现要将其分成M(M≤N)段,并要求每段连续,且每段和的最大值最小. 关于最大值最小: 例如一数列4 2 4 5 1要分成3段 将其如下分段: [4 ...

  8. 剑指Offer面试题:8.斐波那契数列

    一.题目:斐波那契数列 题目:写一个函数,输入n,求斐波那契(Fibonacci)数列的第n项.斐波那契数列的定义如下: 二.效率很低的解法 很多C/C++/C#/Java语言教科书在讲述递归函数的时 ...

  9. 代码的坏味道(4)——过长参数列(Long Parameter List)

    坏味道--过长参数列(Long Parameter List) 特征 一个函数有超过3.4个入参. 问题原因 过长参数列可能是将多个算法并到一个函数中时发生的.函数中的入参可以用来控制最终选用哪个算法 ...

  10. 算法: 斐波那契数列C/C++实现

    斐波那契数列: 1,1,2,3,5,8,13,21,34,....     //求斐波那契数列第n项的值 //1,1,2,3,5,8,13,21,34... //1.递归: //缺点:当n过大时,递归 ...

随机推荐

  1. 使用Node.js打造交互式脚手架,简化模板下载与项目创建

    在上一篇文章中,我们探讨了如何构建一个通用的脚手架框架.今天,我们将在此基础上进一步扩展脚手架的功能,赋予它下载项目模板的能力. 通常情况下,我们可以将项目模板发布到 npm 上,或者在公司内部利用私 ...

  2. [AI/GPT/综述] AI Agent的设计模式综述

    序:文由 其一,随着大模型的发展,通用智能不断迭代升级,应用模式也不断创新,从简单的Prompt应用.RAG(搜索增强生成).再到AI Agent(人工智能代理). 其中AI Agent一直是个火热的 ...

  3. 常用损失函数 LossFunction

    文章结构 损失函数在神经网络中的位置 常用的损失函数(结构:解释,公式,缺点,适用于,pytorch 函数) MAE/L1 Loss MSE/L2 Loss Huber Loss 对信息量.熵的解释 ...

  4. k8s Error: failed to prepare subPath for volumeMount "custom-logo" of container "grafana"

    前言 使用 k8s 挂载卷文件时,使用了 hostPath,type: File volumeMounts: - mountPath: /usr/share/grafana/public/img/gr ...

  5. 【Vue3】下载zip文件损坏的问题

    需求: 需要在vue3上实现Asp.net Web API 下载zip包的功能,本身需求很简单,但是中间遇到了问题,记录一下. 问题: 下载的zip包和后端的zip包大小不一致,后端生成的zip 61 ...

  6. PX4 仿真环境开发整理

    博客地址:https://www.cnblogs.com/zylyehuo/ (一)PX4 仿真开发 搭建仿真环境 概念介绍及环境建议 MAVROS安装(适用于ROS1.ROS2) Ubuntu安装Q ...

  7. EBUSY: resource busy or locked, rmdir

    方案一: 方案二: !!! 出现问题后,千万不要忽略npm提示你的警告... 如果以上两种方案还未解决,那么大概率是因为你的npm版本较低导致的,升级你的npm. cnpm install -g np ...

  8. Delphi 使用API函数AnimateWindow实现窗体特效功能

    API函数 AnimateWindow 使用: 函数功能:窗体显示和隐藏时产生特殊的动画效果:可以产生两种类型的动画效果: 滚动动画 和 滑动动画 函数原型:BOOL AnimateWindow(HW ...

  9. vue3-webseek网页版AI问答|Vite6+DeepSeek+Arco流式ai聊天打字效果

    2025 AI实战vue3+deepseek+arcoDesign仿DeepSeek/豆包网页版AI聊天助手. vue3-web-deepseek 实战网页PC版智能AI对话,基于vite6+vue3 ...

  10. 【软件】基于JSP和Bootstrap的潇湘博客平台

    潇湘博客平台 XiaoXiangBlog 说明 Eclipse 项目 - Version: 2020-06 (4.16.0). JDK8. 潇湘博客(XiaoXiangBlog) 一个简单的Java ...