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


题意

给定正小数 \(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. 聊聊wireshark的进阶使用功能

    1. 前言 emmm,说起网络知识学习肯定离不来wireshark工具,这个工具能够帮助我们快速地定位网络问题以及帮助正在学习网络协议这块的知识的同学验证理论与实际的一大利器,平时更多的只是停留在初步 ...

  2. 大白话带你认识JVM(转)

    转自微信公众号(JavaGuide) 前言 如果在文中用词或者理解方面出现问题,欢迎指出.此文旨在提及而不深究,但会尽量效率地把知识点都抛出来 一.JVM的基本介绍 JVM 是 Java Virtua ...

  3. PPT太大发不出去?教你三个PPT压缩方法,200M的PPT变15M

    相信有很多小伙伴在工作的时候,都会制作不少的PPT,而我们也知道很多PPT在制作完成以后,体积就会变得非常大,在发送给别人的时候总是会受到限制,是有点难搞了. 别担心,今天小编将告诉大家三个简单的方法 ...

  4. 用MMCls训练手势模型

    import os import json import mmcv import time from mmcv import Config from mmdet.apis import inferen ...

  5. LVS+keepalived配置高可用架构和负载均衡机制(1)

    一.基础知识 1. 四层负载均衡(基于IP+端口的负载均衡) 所谓四层负载均衡,也就是主要通过报文中的目标ip地址和端口,再加上负载均衡设备设置的服务器选择方式(分发策略,轮询),决定最终选择的内部服 ...

  6. [ABC205E] White and Black Balls 题解

    White and Black Balls 题目大意 将 \(n\) 个白球,\(m\) 个黑球排成一列,要求满足 \(\forall i\in[1,n+m],w_i\le b_i+k\),问存在多少 ...

  7. 挑战程序设计竞赛 2.2 poj 3040 Allowance 贪心

    https://vjudge.csgrandeur.cn/problem/POJ-3040 /* 作为创纪录的牛奶产量的奖励,约翰决定每周给贝西一小笔零用钱.FJ拥有一组N(1 <= N < ...

  8. [WPF]原生TabControl控件实现拖拽排序功能

    在UI交互中,拖拽操作是一种非常简单友好的交互.尤其是在ListBox,TabControl,ListView这类列表控件中更为常见.通常要实现拖拽排序功能的做法是自定义控件.本文将分享一种在原生控件 ...

  9. k8s-服务网格实战-入门Istio

    背景 终于进入大家都比较感兴趣的服务网格系列了,在前面已经讲解了: 如何部署应用到 kubernetes 服务之间如何调用 如何通过域名访问我们的服务 如何使用 kubernetes 自带的配置 Co ...

  10. DDD技术方案落地实践

    1. 引言 从接触领域驱动设计的初学阶段,到实现一个旧系统改造到DDD模型,再到按DDD规范落地的3个的项目.对于领域驱动模型设计研发,从开始的各种疑惑到吸收各种先进的理念,目前在技术实施这一块已经基 ...