首页 > 其他分享 >P5179 Fraction 题解

P5179 Fraction 题解

时间:2023-05-20 12:56:26浏览次数:53  
标签:分数 now int 题解 long double Fraction dfrac P5179

题目描述

给你四个正整数 \(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}{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 的一张图。

Stern-Brocot树

刚才所提到的不存在的节点就是虚线相连的那些节点。可以看到,这棵树具有二叉结构。因此在这棵树上搜索只需要花费 \(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}\)。解一下这个不等式:

\[\begin{array}{c} \dfrac{x + ct}{y + dt} \ge \dfrac{A}{B} \\\\ (x + ct)B \ge (y + dt)A \\\\ xB + cBt \ge yA + dAt \\\\ t \ge \dfrac{yA - xB}{cB - dA} \end{array} \]

同理,如果 \(\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 最接近的分数。

标签:分数,now,int,题解,long,double,Fraction,dfrac,P5179
From: https://www.cnblogs.com/LcyRegister/p/17417052.html

相关文章

  • CF1781F题解
    \(\text{link}\)。也是一道非常巧妙的\(\texttt{dp}\)。容易想到把括号变成\(\pm1\)。考虑括号序列合法等价于前缀和\(\ge0\),我们可以想加入\(()\)或\()(\)对前缀的影响。设加入的位置的前一位前缀和为\(x\),则加入\(()\)相当于把\(x\)替换为\([x,x+1,x]\),加入......
  • CSP-J2021试题题解
    1.分糖果原题:https://www.luogu.com.cn/problem/P7909原代码:#include<bits/stdc++.h>#definelllonglongusingnamespacestd;lln,l,r;intmain(){ cin>>n>>l>>r; if(r%n==n-1)cout<<n-1; elseif(l%n==n-1)cout<<n-1; elseif(......
  • 问题解一
    (1)用pycharm连接mysql时,需要安装一个驱动,点击连接界面,如果TestConnection不可用,点击右边的按钮即可(2)如果要将同一网站不同链接的数据存入数据库,可考虑重写start_requestseg:forindex,hrefinenumerate(self.start_urls):ifindex==0:yieldscrapy.Request(......
  • 僵尸进程问题解决
    1何为僵尸进程僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出,子进程被init接管,子进程退出后init会回收其占用的相关资源。在UNIX系统中,一个进程结束了,但是它的父进程没有等待(调用wait/wai......
  • 【P4331 [BalticOI 2004]】Sequence 数字序列 题解(左偏树维护动态区间中位数)
    左偏树维护动态区间中位数。传送门P4331BalticOI2004Sequence数字序列。Solution1我的思路和题解前半部分完全重合了((如果按照单调不增去分割\(a\)序列的话,对于每一段我们能很简单地得出它的最佳答案:中位数。发现严格单调很难做,很难拿捏,考虑对\(a\)序列的每一项都进......
  • CSP-J2022山东补赛题解
    1.植树节原题:https://www.luogu.com.cn/problem/U285015代码:#include<bits/stdc++.h>#definelllonglongusingnamespacestd;constintN=1e6+255;inta[N],n,x,y,maxb=-1e9,ans=-1e9;intmain(){ cin>>n; for(inti=1;i<=n;i++){ cin>>x&g......
  • CSP-J2019试题题解
    1.数字游戏原题:https://www.luogu.com.cn/problem/P5660代码:#include<bits/stdc++.h>#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>usingnamespacestd;strings;intmain(){ cin>>s;intnum=0; fo......
  • CF1512C A-B Palindrome 题解
    CF1512CA-BPalindrome题解Link洛谷CodeforcesDescription给出\(T\)个只由0、1和?组成的字符串\(s\),将字符串中的?替换成0或1之后形成一个回文串并且恰好有\(a\)个0和\(b\)个1,无解输出-1。Solution首先,若不考虑?原串不为回文串一定无解,输出-1即......
  • CF1512D Corrupted Array 题解
    CF1512DCorruptedArray题解Link洛谷CodeforcesDescription给定一个正整数\(n\)和长度为\(n+2\)的数组\(b\),数组\(b\)是依据如下算法构造的:随机生成一个含有\(n\)个元素的原始数组\(a\);把数组\(a\)赋值给数组\(b\),即\(b_i=a_i(1\lei\len)\);数组\(b\)......
  • 【题解】第五届图灵杯
    //createdon23.05.13目录A.差值B.基础循环结构练习题C.基础计算几何练习题D.永恒悲剧A.差值考虑求长度为\(i\)的答案,肯定是长度\(\geqi\)的后缀,按照字典序排序后,相邻两个求解。所以考虑倒着扫,每次对于\(i\)的后缀找到排名前后第一个后缀,求出两个长度为\(i\)......