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 ...
随机推荐
- Go 项目代码布局
Go 项目代码布局 目录 Go 项目代码布局 一.Go 语言"创世项目"结构 1.1 src 目录结构三个特点 二.Go 项目布局演进 2.1 演进一:Go 1.4 版本删除 pk ...
- FragmentStatePagerAdapter
public abstract class FragmentStatePagerAdapter extends PagerAdapter java.lang.Object ↳ android.s ...
- Vue源码学习(十三):nextTick()方法
好家伙,nextTick, (...这玩意,不太常用) 1.什么是nextTick 在Vue中,nextTick是一个用于异步执行回调函数的方法. 它在Vue更新DOM后被调用,以确保在下一次DOM更 ...
- 10.7 多校联测 Day? 总结
打了一场从头到尾稀里糊涂的比赛. 脑子完全不转. 开考看 T1,一开始连逆序对都没看出来.想着把不合法的点两两连边黑白染色(也是够离谱)然后也不知道干了啥(似乎很困),反正一个小时过去了. 就只看出来 ...
- 2022/7/26 暑期集训 pj组第6次%你赛
个人第3次 又是下午打,旁边那帮 不知好歹的 入门组小孩们又在吵吵... T1 老师是不是放反了? T1 是蓝题诶 理所应当地 跳过 然后就忘了写了,连样例也没打...样例可是有7分诶! 到现在也没写 ...
- shell脚本之语句(条件、循环)
条件语句 1.测试 使用[]时要使用空格,注意格式 格式1:test 条件表达式 格式2:[ 条件表达式 ]#注意空格 注意[]空格,否则会失败 测试 是否成功使用 $?返回值来判断 [ 操 ...
- Html文本学习内容-2
(一)文本 1.大小写转换 text-transform属于处理文本的大小写,有4个值: none(默认值) uppercase(全部大写) lowercase(全部小写) capitalize(首字 ...
- C#操作Microsoft.Office.Interop.Word类库完整例子
使用Microsoft.Office.Interop.Word类库操作wor文档 一.准备工作 首先在工厂中,引用[Microsoft.Office.Interop.Word],本地安装了world, ...
- CSP-S 考前备战——常考知识点串烧
1.树形结构 与 树形dp PS :在CSP-S 2019,CSP-J 2020,CSP-S 2020,CSP-S 2021 均有考查 此类问题的做题方法就是将问题转化成树上的问题,然后进行深度优先遍 ...
- CF1592C. Bakry and Partitioning
原题链接:CF1592C. Bakry and Partitioning 题意: 给定一个\(n\)个点,\(n - 1\)条边的树,并且每个点都有权值\(w_i\),让你最少割掉一条边最多割掉\(k ...