P5179 Fraction 题解
题目描述
给你四个正整数 \(a,\,b,\,c,\,d\) ,求一个最简分数 \(\frac{p}{q}\) 满足 \(\frac{a}{b} < \frac{p}{q} < \frac{c}{d}\)。
若有多组解,输出 \(q\) 最小的一组,若仍有多组解,输出 \(p\) 最小的一组。
前置知识:Stern-Brocot 树
首先引入分数逼近。这里的分数逼近是指用用一个分数来逼近另一个分数,使得误差趋于零。例如,假设需要逼近的分数为 \(\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)\) 的时间。非常优秀。
关于最简性的证明可以看 OI-wiki 上的解释。这里不再赘述。
对于这道题,显然可以在 Stern-Brocot 树上二分来求解。具体的,如果当前结果 \(\dfrac{x}{y}\) 在左端点 \(\dfrac{A}{B}\) 的左边,则向右递归,反之亦然。于是可以写出这样的代码:
void solve(int a = 0, int b = 1, int c = 1, int d = 0) {
int x = a + c, y = b + d;
long double now = (long double)x / y;
long double L = (long double)A / B, R = (long double)C / D;
if (now > L && now < R) {
ans = {x, y}; return;
}
if (now <= L) solve(x, y, c, d);
else solve(a, b, x, y);
}
交上去以后发现只有 \(60\) 分。说明我们需要继续优化算法。
如果把递归时的路径打印出来,我们发现可能会连续地向左(向右)递归很多次。这很不好,因为浪费了许多时间。那么是否可以用较短的复杂度计算出接下来需要连续向左(向右)递归多少次呢?
答案是可以的。假设当前的递归函数是 \((a, b, c, d)\),当前分数 \(\dfrac{x}{y} = \dfrac{a + c}{b + d}\)。假设 \(\dfrac{A}{B} < \dfrac{x}{y} < \dfrac{C}{D}\),这是最好的,可以直接输出了。但是如果 \(\dfrac{x}{y} \le \dfrac{A}{B}\),显然需要向右递归。假设向右递归的次数为 \(t\),那么 \(\dfrac{x + ct}{y + dt} \ge \dfrac{A}{B}\)。解一下这个不等式:
\]
同理,如果 \(\dfrac{x}{y} \ge \dfrac{C}{D}\),那么需要连续向左递归的次数 \(t \ge \dfrac{yC - xD}{aD - bC}\)。
如此,我们用 \(O(1)\) 的时间求出了连续向左(向右)递归的次数。
代码
#include <algorithm>
#include <cstdio>
#include <iostream>
using namespace std;
using PII = pair<int, int>;
PII ans;
int A, B, C, D;
void solve(int a = 0, int b = 1, int c = 1, int d = 0) {
int x = a + c, y = b + d;
long double now = (long double)x / y;
long double L = (long double)A / B, R = (long double)C / D;
if (now > L && now < R) {
ans = {x, y}; return;
}
if (now <= L) {
int t = (int)(y * A - x * B) / (c * B - d * A);
solve(x + c * t, y + d * t, c, d);
}
else {
int t = (int)(y * C - x * D) / (a * D - b * C);
solve(a, b, x + a * t, y + b * t);
}
}
signed main() {
while (scanf("%d%d%d%d", &A, &B, &C, &D) != EOF) {
solve();
printf("%d/%d\n", ans.first, ans.second);
}
return 0;
}
简短精炼得代码后面有个小坑:别忘了用 long double。
最后留个 Stern-Brocot 树的练习题:P1298 最接近的分数。
P5179 Fraction 题解的更多相关文章
- Infinite Fraction Path HDU 6223 2017沈阳区域赛G题题解
题意:给你一个字符串s,找到满足条件(s[i]的下一个字符是s[(i*i+1)%n])的最大字典序的长度为n的串. 思路:类似后缀数组,每次倍增来对以i开头的字符串排序,复杂度O(nlogn).代码很 ...
- 题解-AtCoder-agc003F Fraction of Fractal(非矩阵快速幂解法)
Problem AtCoder-agc003F 题意:给出\(n\)行\(m\)列的01矩阵,一开始所有 \(1\) 连通,称此为\(1\)级分形,定义\(i\)级分形为\(i-1\)级分形中每个标示 ...
- 【题解】Unit Fraction Partition-C++
Description给出数字P,Q,A,N,代表将分数P/Q分解成至多N个分数之和,这些分数的分子全为1,且分母的乘积不超过A.例如当输入数据为2 3 120 3时,我们可以得到以下几种分法: In ...
- [题解]codevs1001 舒适的路线
h3 { font-family: Consolas; color: #339966 } .math { font-family: Consolas; color: gray } 题目描述 Descr ...
- LeetCode Fraction to Recurring Decimal
原题链接在这里:https://leetcode.com/problems/fraction-to-recurring-decimal/ 题目: Given two integers represen ...
- Codeforces Round #172 (Div. 2) B. Nearest Fraction 二分
B. Nearest Fraction Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/281/p ...
- 166. Fraction to Recurring Decimal
题目: Given two integers representing the numerator and denominator of a fraction, return the fraction ...
- leetcode & lintcode 题解
刷题备忘录,for bug-free 招行面试题--求无序数组最长连续序列的长度,这里连续指的是值连续--间隔为1,并不是数值的位置连续 问题: 给出一个未排序的整数数组,找出最长的连续元素序列的长度 ...
- LeetCode All in One题解汇总(持续更新中...)
突然很想刷刷题,LeetCode是一个不错的选择,忽略了输入输出,更好的突出了算法,省去了不少时间. dalao们发现了任何错误,或是代码无法通过,或是有更好的解法,或是有任何疑问和建议的话,可以在对 ...
- 2017 ACM/ICPC 沈阳 G题 Infinite Fraction Path
The ant Welly now dedicates himself to urban infrastructure. He came to the kingdom of numbers and s ...
随机推荐
- Java 21的StringBuilder和StringBuffer新增了一个repeat方法
发现Java 21的StringBuilder和StringBuffer中多了repeat方法: /** * @throws IllegalArgumentException {@inheritDoc ...
- Go with Protobuf
原文在这里. 本教程为 Go 程序员提供了使用Protocol buffer的基本介绍. 本教程使用proto3向 Go 程序员介绍如何使用 protobuf.通过创建一个简单的示例应用程序,它向你展 ...
- PostgreSQL学习笔记-2.基础知识:INSERT、SELECT、运算符、表达式、约束
PostgreSQL INSERT INTO 语句用于向表中插入新记录,兼容SQL通用语法. 语法 INSERT INTO 语句语法格式如下: INSERT INTO TABLE_NAME (colu ...
- 11. 用Rust手把手编写一个wmproxy(代理,内网穿透等), 实现健康检查
11. 用Rust手把手编写一个wmproxy(代理,内网穿透等), 实现健康检查 项目 ++wmproxy++ gite: https://gitee.com/tickbh/wmproxy gith ...
- Go 代码块与作用域,变量遮蔽问题详解
Go 代码块与作用域详解 目录 Go 代码块与作用域详解 一.引入 二.代码块 (Block) 2.1 代码块介绍 2.2 显式代码块 2.3 隐式代码块 2.4 空代码块 2.5 支持嵌套代码块 三 ...
- 轻松掌握组件启动之Redis集群扩展秘籍:轻松扩容与缩容,释放高性能潜能
扩展集群操作 扩容 在我们原始的集群基础上,我们决定增加一台主节点(8007)和一台从节点(8008),这样新增的节点将会在下图中以虚线框的形式显示在集群中. 1: 首先,在 /usr/local/r ...
- git 删除远程分支,重新提交代码
最近提交代码,分支名出错了,要更正分支名并且重新提交代码,这里记录一下. 说明一下,我之前的分支名是:feature_mobile_duty,更正后的分支名是feature-mobile-duty,是 ...
- Velocity之Hello World(tomcat下配置Velocity)
本文主要参考:http://hi.baidu.com/dalianjingying/item/1fb3a98ad64dcac299255f72 http://wangbaoaiboy.blog.163 ...
- Util应用框架基础(五) - 异常处理
本节介绍Util应用框架如何处理系统错误. 概述 系统在运行过程中可能发生错误. 系统错误可以简单分为两类: 系统异常 系统本身出现的错误. 业务异常 不满足业务规则出现的错误. 如何处理系统异常 如 ...
- vue 中引入pingfang字体 或者其他字体 支持ttf otf格式
新建一个font 文件 里面放字体文件 可以百度搜索你想要的字体下载下来 一般10m左右 新建一个font.css 里面配置字体 @font-face { font-family: 'PF'; ...