算是水紫,不过也学到一些有用的东西。


题意

给定正小数 \(N\)。求分子不大于 \(n\),分母不大于 \(m\) 的分数 \(\dfrac{n}{m}\),使得 \(\dfrac{n}{m}\) 的值与 \(N\) 最接近(这里的最接近指的是 \(|\dfrac{n}{m} - N|\) 最小)。

分析

首先,大部分人都可以想到一个暴力:枚举 \(i \in [1, m]\) 作为分子,计算出最佳分母 \(r_1 = \lfloor i \times N \rfloor, r_2 = \lceil i \times N\rceil\)。把 \(r_1, r_2\) 分别带进去看看那个更优就完事了。出去判边界之类的问题,复杂度 \(O(m)\)。由于 \(m \leq 10 ^ 7\),直接就艹过去了。如果这个都不会写可以看这里:暴力算法

然而我们肯定不会满足于这样的暴力算法。来点优雅的算法吧。


引入分数逼近。这里的分数逼近是指用用一个分数来逼近另一个分数,使得误差趋于零。例如,假设需要逼近的分数为 \(\dfrac{r}{s}\),有分数 \(\dfrac{u}{v} > \dfrac{r}{s}\)。那么有以下结论:

\[\dfrac{r}{s} \leq \dfrac{r + u}{s + v} \leq \dfrac{u}{v}
\]

具体等号能不能取到记不清了,不过不影响。结论很好证明,下面证一下。


将 \(\dfrac{r + u}{s + v}\) 与 \(\dfrac{r}{s}\) 做减法,得到 \(\dfrac{r + u}{s + v} - \dfrac{r}{s} = \dfrac{(r + u)s - r(s + v)}{s(s + v)} = \dfrac{us- vr}{s(s + v)}\)。

因为 \(\dfrac{r}{s} < \dfrac{u}{v}\),两边同时乘以 \(sv\),得 \(vr < us\),即 \(us - vr > 0\)。

又因为 \(s(s + v) > 0\),所以 \(\dfrac{us - vr}{s(s + v)} > 0\)。证毕。

注意上面结论和证明成立的条件是 \(u, v, s, r > 0\)。


接下来引入 Stern-Brocot 树这个概念。

Stern-Brocot 树可以维护所有的正分数。这一点可以被我们用来解决这道题目。

首先介绍一下 Stern-Brocot 树。这个树由 \(\dfrac{0}{1}\) 和 \(\dfrac{1}{0}\) 两个分数开始。\(\dfrac{1}{0}\) 不大好定义,暂且把它当做 \(+ \infty\)。将这两个分数作为源节点

接下来,像我们刚才讨论的分数逼近,将 \(\dfrac{0}{1}\) 和 \(\dfrac{1}{0}\) 的分子分母分别相加,得到另外一个分数 \(\dfrac{1}{1}\)。这个分数确实在 \(\dfrac{0}{1}\) 与 \(\dfrac{1}{0}\) 之间。\(\dfrac{1}{1}\) 被成为第 \(1\) 层迭代后的节点。

同样的,将 \(\dfrac{1}{1}\) 与 \(\dfrac{0}{1}, \dfrac{1}{0}\) 分别进行操作,得到两个分数,称为第二次迭代。

所以我们得到了 Stern-Brocot 树的构建基础:将 \(\dfrac{a}{b}\) 与 \(\dfrac{c}{d}\) 分子分母分别相加,得到 \(\dfrac{a + c}{b + d}\) 作为下一轮迭代的节点。

例如,进行三次操作后,这棵树就会变成这样:

\[\begin{array}{c} \dfrac{0}{1}, \dfrac{1}{1}, \dfrac{1}{0} \\\\ \dfrac{0}{1}, \dfrac{1}{2}, \dfrac{1}{1}, \dfrac{2}{1}, \dfrac{1}{0} \\\\ \dfrac{0}{1}, \dfrac{1}{3}, \dfrac{1}{2}, \dfrac{2}{3}, \dfrac{1}{1}, \dfrac{3}{2}, \dfrac{2}{1}, \dfrac{3}{1}, \dfrac{1}{0} \end{array}
\]

注意,某些节点(就是第 \(i\) 层存在,第 \(i + 1\) 层也存在的节点),实际上在第 \(i + 1\) 层是不会出现的。只是为了方便比较加了上去。

可以看到,第三层的第二个分数 \(\dfrac{1}{3}\) 就是左右两边两个数分子分母分别相加的和。第四个,第六个和第八个以此类推。

下面是来自 OI-wiki 的一张图。

刚才所提到的不存在的节点就是虚线相连的那些节点。可以看到,这棵树具有二叉结构。因此在这棵树上搜索只需要花费 \(O(\log_2 n)\) 的时间。非常优秀。这样对于这道题,我们就可以把小数 \(N\) 从第一层开始向下搜索。如果当前节点值大于 \(N\),那么向左递归。否则向右递归,直到分子或分母大于 \(n\) 或 \(m\)。时间复杂度肯定是 \(O(\log n)\)。(假设 \(n, m\) 同阶)。

关于最简性的证明可以看 OI-wiki 上的解释。这里不再赘述。


这道题的思路就讲解完了。注意别忘了判断多解的情况。由于刚才提到,Stern-Brocot 树具有最简性,因此放心的判断当前分数值与 \(N\) 的误差和原来的是否一样就可以了。

卡常顺便卡了个 rank1。欢迎来踩。

代码

#include <algorithm>
#include <cstdio> using PII = std::pair<int, int>;
double N, m_error;
int n, m;
PII ans(0, 1);
bool flag = false; double fabs(double x) {
return x < 0 ? -x : x;
}
inline void get(double N, int a = 0, int b = 1, int c = 1, int d = 0) {
int x = a + c, y = b + d;
if (x > n || y > m) return;
double error = (double)x / y - N;
if (fabs(error) == m_error) flag = true;
if (fabs(error) < m_error) {
flag = false; ans = {x, y}; m_error = fabs(error);
if (error == 0) return;
}
if (error < 0) get(N, x, y, c, d);
else get(N, a, b, x, y);
} int main() {
scanf("%d%d", &n, &m);
scanf("%lf", &N); m_error = N; get(N);
if (flag) puts("TOO MANY");
else printf("%d/%d", ans.first, ans.second);
return 0;
}

明天就是五一劳动节。在这里提前祝大家五一快乐,多多点赞。

Luogu P1298 最接近的分数 做题记录的更多相关文章

  1. 洛谷P1298 最接近的分数

    P1298 最接近的分数 题目描述 给出一个正小数,找出分子(非负)不超过M,分母不超过N(正数)的最简分数或整数,使其最接近给出的小数.“最接近”是指在数轴上该分数距离给出的小数最近,如果这个分数不 ...

  2. Sam做题记录

    Sam做题记录 Hihocoder 后缀自动机二·重复旋律5 求一个串中本质不同的子串数 显然,答案是 \(\sum len[i]-len[fa[i]]\) Hihocoder 后缀自动机三·重复旋律 ...

  3. 退役II次后做题记录

    退役II次后做题记录 感觉没啥好更的,咕. atcoder1219 历史研究 回滚莫队. [六省联考2017]组合数问题 我是傻逼 按照组合意义等价于\(nk\)个物品,选的物品\(\mod k\) ...

  4. BJOI做题记录

    BJOI做题记录 终于想起还要做一下历年省选题了2333 然而咕了的还是比做了的多2333 LOJ #2178. 「BJOI2017」机动训练 咕了. LOJ #2179. 「BJOI2017」树的难 ...

  5. FJOI2017前做题记录

    FJOI2017前做题记录 2017-04-15 [ZJOI2017] 树状数组 问题转化后,变成区间随机将一个数异或一,询问两个位置的值相等的概率.(注意特判询问有一个区间的左端点为1的情况,因为题 ...

  6. UOJ 做题记录

    UOJ 做题记录 其实我这么弱> >根本不会做题呢> > #21. [UR #1]缩进优化 其实想想还是一道非常丝播的题目呢> > 直接对于每个缩进长度统计一遍就好 ...

  7. project euler做题记录

    ProjectEuler_做题记录 简单记录一下. problem 441 The inverse summation of coprime couples 神仙题.考虑答案为: \[\begin{a ...

  8. 退役IV次后做题记录

    退役IV次后做题记录 我啥都不会了.... AGC023 D 如果所有的楼房都在\(S\)同一边可以直接得出答案. 否则考虑最左最右两边的票数,如果左边>=右边,那么最右边会投给左边,因为就算车 ...

  9. 退役III次后做题记录(扯淡)

    退役III次后做题记录(扯淡) CF607E Cross Sum 计算几何屎题 直接二分一下,算出每条线的位置然后算 注意相对位置这个不能先搞出坐标,直接算角度就行了,不然会卡精度/px flag:计 ...

  10. [日记&做题记录]-Noip2016提高组复赛 倒数十天

    写这篇博客的时候有点激动 为了让自己不颓 还是写写日记 存存模板 Nov.8 2016 今天早上买了两个蛋挞 吃了一个 然后就做数论(前天晚上还是想放弃数论 但是昨天被数论虐了 woc noip模拟赛 ...

随机推荐

  1. 编译python为可执行文件遇到的问题:使用python-oracledb连接oracle数据库时出现错误:DPY-3010

    错误原文: DPY-3010: connections to this database server version are not supported by python-oracledb in ...

  2. Arduino Uno Rev3

    Overview Arduino Uno is a microcontroller board based on the ATmega328P (datasheet). It has 14 digit ...

  3. 安装 mysql-community-server报错

    错误1. 报错: 所有的匹配结果均已经被参数的模块化过滤条件筛除: mysql-community-server 错误:没有任何匹配: mysql-community-server 解决办法: yum ...

  4. DPDK-22.11.2 [四] Virtio_user as Exception Path

    因为dpdk是把网卡操作全部拿到用户层,与原生系统驱动不再兼容,所以被dpdk接管的网卡从系统层面(ip a/ifconfig)无法看到,同样数据也不再经过系统内核. 如果想把数据再发送到系统,就要用 ...

  5. nvm的安装及使用(入门级)

    1 从官网下载压缩包到本地 下载地址: https://github.com/coreybutler/nvm-windows/releases 2 配置 2.1 settings.txt配置 (1)下 ...

  6. 博弈论(Nim游戏 , 有向图游戏)

    博弈论专题 Nim游戏 内容: 有 n 堆石子,每堆石子的石子数给出,甲乙两人回合制取石子,每次可以取任意一堆石子的任意多个(可以直接取完,但不能不取),每个人都按照最优策略来取(抽象),问先手必胜或 ...

  7. 模拟退火算法(SA)

    求某个目标函数的最值 爬山法 首先我们通过爬山法来引出模拟退火算法 我们先看一个例子:求函数的最值 我们用爬山法解决这个问题的步骤 1.在解空间中随机生成一个初始解(图中小黄点就是我们生成的初始解) ...

  8. Apollo 配置中心的部署与使用经验

    前言 Apollo(阿波罗)是携程开源的分布式配置管理中心. 本文主要介绍其基于 Docker-Compose 的部署安装和一些使用的经验 特点 成熟,稳定 支持管理多环境/多集群/多命名空间的配置 ...

  9. 2023-11-08:用go语言,字符串哈希原理和实现 比如p = 233, 也就是课上说的选择的质数进制 “ 3 1 2 5 6 ...“ 0 1 2 3 4 hash[0] = 3 * p的0

    2023-11-08:用go语言,字符串哈希原理和实现 比如p = 233, 也就是课上说的选择的质数进制 " 3 1 2 5 6 ..." 0 1 2 3 4 hash[0] = ...

  10. 格局决定结局,进化还是毁灭,Prompt在其中扮演什么角色

    GPT 时代, Prompt 的价值你们可能不懂 最近, OpenAI 推出了基于 GPT 模型的 GPTs 以及 Agent Stroe 系统,引发广泛关注.业内讨论热点主要集中在吸引用户体验方面. ...