Luogu P1298 最接近的分数 做题记录
算是水紫,不过也学到一些有用的东西。
题意
给定正小数 \(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 + 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}\) 作为下一轮迭代的节点。
例如,进行三次操作后,这棵树就会变成这样:
\]
注意,某些节点(就是第 \(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 最接近的分数 做题记录的更多相关文章
- 洛谷P1298 最接近的分数
P1298 最接近的分数 题目描述 给出一个正小数,找出分子(非负)不超过M,分母不超过N(正数)的最简分数或整数,使其最接近给出的小数.“最接近”是指在数轴上该分数距离给出的小数最近,如果这个分数不 ...
- Sam做题记录
Sam做题记录 Hihocoder 后缀自动机二·重复旋律5 求一个串中本质不同的子串数 显然,答案是 \(\sum len[i]-len[fa[i]]\) Hihocoder 后缀自动机三·重复旋律 ...
- 退役II次后做题记录
退役II次后做题记录 感觉没啥好更的,咕. atcoder1219 历史研究 回滚莫队. [六省联考2017]组合数问题 我是傻逼 按照组合意义等价于\(nk\)个物品,选的物品\(\mod k\) ...
- BJOI做题记录
BJOI做题记录 终于想起还要做一下历年省选题了2333 然而咕了的还是比做了的多2333 LOJ #2178. 「BJOI2017」机动训练 咕了. LOJ #2179. 「BJOI2017」树的难 ...
- FJOI2017前做题记录
FJOI2017前做题记录 2017-04-15 [ZJOI2017] 树状数组 问题转化后,变成区间随机将一个数异或一,询问两个位置的值相等的概率.(注意特判询问有一个区间的左端点为1的情况,因为题 ...
- UOJ 做题记录
UOJ 做题记录 其实我这么弱> >根本不会做题呢> > #21. [UR #1]缩进优化 其实想想还是一道非常丝播的题目呢> > 直接对于每个缩进长度统计一遍就好 ...
- project euler做题记录
ProjectEuler_做题记录 简单记录一下. problem 441 The inverse summation of coprime couples 神仙题.考虑答案为: \[\begin{a ...
- 退役IV次后做题记录
退役IV次后做题记录 我啥都不会了.... AGC023 D 如果所有的楼房都在\(S\)同一边可以直接得出答案. 否则考虑最左最右两边的票数,如果左边>=右边,那么最右边会投给左边,因为就算车 ...
- 退役III次后做题记录(扯淡)
退役III次后做题记录(扯淡) CF607E Cross Sum 计算几何屎题 直接二分一下,算出每条线的位置然后算 注意相对位置这个不能先搞出坐标,直接算角度就行了,不然会卡精度/px flag:计 ...
- [日记&做题记录]-Noip2016提高组复赛 倒数十天
写这篇博客的时候有点激动 为了让自己不颓 还是写写日记 存存模板 Nov.8 2016 今天早上买了两个蛋挞 吃了一个 然后就做数论(前天晚上还是想放弃数论 但是昨天被数论虐了 woc noip模拟赛 ...
随机推荐
- C/C++基础——引用与指针有什么区别?C++中输入输出加速
文章目录 1 引用与指针有什么区别? 2 C++中输入输出加速 tie sync_with_stdio 应用 1 引用与指针有什么区别? 指针和引用都是地址的概念,指针指向一块内存,它的内容是所指内存 ...
- 爬虫系列——selenium
文章目录 一 介绍 二 安装 三 基本使用 四 选择器 五 等待元素被加载 六 元素交互操作 七 其他 八 项目练习 一 介绍 selenium最初是一个自动化测试工具,而爬虫中使用它主要是为了解决r ...
- 若依(ruoyi)开源系统-多数据源问题踩坑实录
内容概要 上一节内容 介绍了用开源系统若依(ruoyi)搭建页面的过程.在实际项目中,经常遇到多数据源后者主从库的情况.本节记录若依多数据源配置过程中遇到的问题排查过程. 背景描述 1.上一节在r ...
- postman导入请求到jmeter进行简单压测,开发同学一学就会
背景 这个事情也是最近做的,因为线上nginx被我换成了openresty,然后接入层服务也做了较大改动,虽然我们这个app(内部办公类)并发不算高,但好歹还是压测一下,上线时心里也稳一点. 于是用j ...
- 如何在虚拟机上安装linux操纵系统
1.下载linux操作系统的镜像文件(iso文件),官网链接(CentOS Mirrors List) (3)下载大小为4G 或者4.几G的iso镜像文件 2.下载我发的VMware Workstat ...
- 读写分离-mycat
读写分离-mycat: 安装mycat: http://dl.mycat.io/1.6.7.1/Mycat-server-1.6.7.1-release-20190627191042-linux.ta ...
- 浅析KV存储之长尾时延解决办法
本文分享自华为云社区<浅析KV存储之长尾时延问题,华为云 GeminiDB Redis 探寻行业更优解决方案!>,作者:华为云数据库GaussDB NoSQL团队. 目前,KV存储的广泛使 ...
- 热烈祝贺:薪火数据(https://www.datainside.com.cn 数据中心低代码搭建平台)参加教育博览会取得圆满成功。
- 记一次逆向分析解密还原Class文件
前言 前阵子我的一位朋友发来一份代码让我帮忙看看.具体就是所有的jsp文件内容和大小都一样,漏洞挖掘无从下手.经过分析发现所有的Class都使用了自定义的加密工具加密,经过逆向分析,顺利解密,因而有了 ...
- centos虚拟机安装
目录 一.准备工作 1.vmware workstation软件安装 2.准备ios镜像 二.创建Centos虚拟机 三.进行Centos7的系统安装 四.虚拟机快照的使用 1.创建虚拟机快照 2.还 ...