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 ... 
随机推荐
- IP协议:连接你我,掌握互联网的关键
			IP 基本认识 在之前的章节中,我们已经详细介绍了应用层和传输层的相关概念和原理,了解了进程之间如何进行可靠的数据传输.我们知道,传输层的头部包含了进程所使用的端口信息,这是为了确保数据能够正确地传递 ... 
- oracle 12C提示:ORA-28001口令已经失效
			oracle 12C 提示口令已经失效,此用户是pdb用户,解决办法:1 系统管理员身份登陆 sqlplus / as sysdba 2 转到对应的pdb容器中 alter session set c ... 
- 如何查询4GL程序中创建的临时表中的数据
			前提:将dba_segments这个表的select权限授权给各个营运中心(即数据库用户) ①.用sys账号以dba的权限登录数据库 <topprod:/u1/topprod/tiptop> ... 
- Factors  分解质因数
			package com.yourself.yours; import java.util.Scanner; /** ****************************************** ... 
- 麒麟系统开发笔记(十三):在国产麒麟系统上编译OSG库、搭建基础开发环境和移植测试Demo
			前言 在国产麒麟系统上实现C++三维仿真,使用OSG技术,其他基于web的技术也是可以但是交互上鼠标拽托等交互相对差一些,所以这块需要斟酌选择到底是何种技术来取舍. 本篇在厂家指定的麒麟系统上编 ... 
- Tetris(俄罗斯方块).sh
			#!/bin/bash # Tetris Game # 10.21.2003 xhchen<[email]xhchen@winbond.com.tw[/email]> #APP decla ... 
- NOI Linux 食用指北
			写这篇 blog 的原因是某个小朋友要考 CSP 了还不会用 linux,怎么回事呢. 单击图片即可放大. 前置- linux 虚拟机的安装 在官网 / 其他地方下载 VMware. 在 noi 官网 ... 
- Android-Java-反序列化JSON
			import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; String jsonStr= WebAPIOperato ... 
- 【爬虫实战】用Python采集任意小红书笔记下的评论,爬了10000多条,含二级评论!
			目录 一.爬取目标 二.爬虫代码讲解 2.1 分析过程 2.2 爬虫代码 三.演示视频 一.爬取目标 您好!我是@马哥python说 ,一名10年程序猿. 我们继续分享Python爬虫的案例,今天爬取 ... 
- 适合业余爱好者DIY的高精度数字电桥
			基本状况:工作频率: 100Hz,1kHz,7.813kHz最小分辨:最小分辨0.5毫欧,0.03uH,0.02pF最大分辨:G欧基本量程精度:1kHz基本量程精度,0.5%,选好电阻,精心制作,可以 ... 
