题解 P1298 最接近的分数

· · 题解

看了下这个题的题解区。

地铁,老人,手机.jpg。

(我怎么一个都看不懂啊啊啊我好菜)

所以下面介绍一下我的做法。

引入:一道小学奥数题

求最小的 p, q 使得 \dfrac{30}{7} < \dfrac{p}{q} < \dfrac{34}{7}

你不觉得这个假分数看着很难受吗?

处理掉它:\dfrac{2}{7} < \dfrac{p-4q}{q} < \dfrac{6}{7}

然后呢?

聪明的你已经知道了。求倒数!令 p-4q=r

我们再处理掉假分数: $\dfrac{1}{6} < \dfrac{q-r}{r} < \dfrac{5}{2}$。 我们发现此时小于号两边分别是一个真分数和一个假分数。可以直接令 $q-r=1$,$r=1$。 然后我们再逆推回去,可以得到 $p=9,q=2$。 可以发现,类似的方法可以用于求解 $\dfrac{a}{b} < \dfrac{p}{q} < \dfrac{c}{d}$ 中使 $p, q$ 最小的问题。 Code: ```cpp void sol(ll a, ll b, ll c, ll d, ll& p, ll& q) { if (a < b && c > d) { p = 1; q = 1; } else { sol(d % c, c, b - d / c * a, a, q, p); q += d / c * p; } } ``` ## 正式开始 可以发现,上面的方法需要频繁取倒数,所以对精度要求比较高,因此我们将输入的小数用分数表示。这里分母取 $10^{14}$。得到分数 $\dfrac{a}{10^{14}}$。 显然,若在距 $r$ 在 $k$ 的范围之内时,存在满足条件的 $p, q$,那么距离在 $k' > k$ 之内时,也存在满足条件的 $p, q$。找到了单调性,我们可以二分所求答案 $\dfrac{p}{q}$ 与 $r$ 的距离 $k$。 分类讨论答案大于和小于 $r$ 的情况,跑两次二分: 当答案小于 $r$, 用上述方法求满足 $\dfrac{a-k-1}{10^{14}} < \dfrac{p}{q} < \dfrac{a + 1}{10^{14}}$ 的最小 $p, q$。而 `check()` 只需判断 `p <= m && q <= n`。 同理,答案大于 $r$ 时,也可以用类似的方法。 这样我们就解决了求答案的问题。那么怎么判断答案是否唯一呢? 这个也很简单,只要枚举过程中有多个数与答案距离相同,答案就是不唯一的。这一点其他题解已经说得很清楚了。注意枚举过程中可能枚举到同一个数,这种情况是不算重复的。 浮点数有精度误差,所以要判断相差值小于一个极小值,就可以认为相等。(这好像是常识吧?) 最终复杂度 $O(\log^2r)$。 ![](https://cdn.luogu.com.cn/upload/image_hosting/we7l6rfl.png) Code: ```cpp #include <bits/stdc++.h> using namespace std; typedef __int128 ll; typedef long double ld; class IO { template <class T> void write(T a) { if (a > 9) write(a / 10); putchar(a % 10 + '0'); } public: template <class T> IO operator<<(T a) { if (a < 0) { putchar('-'); a = -a; } write(a); return *this; } IO operator<<(char a) { putchar(a); return *this; } template <class T> IO operator>>(T& a) { int sign = 1; a = 0; char c = getchar(); while (c < '0' || c > '9') { if (c == '-') { sign = -1; } c = getchar(); } while (c >= '0' && c <= '9') { a = (a << 1) + (a << 3) + (c ^ 48); c = getchar(); } a *= sign; return *this; } } io; ll m, n; ld fr; ll a; bool flag; ll ansa, ansb; ld ansd; void sol(ll a, ll b, ll c, ll d, ll& p, ll& q) { if (a < b && c > d) { p = 1; q = 1; } else { sol(d % c, c, b - d / c * a, a, q, p); q += d / c * p; } } ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); } ll max(ll a, ll b) { return a > b ? a : b; } int main() { io >> m >> n; scanf("%Lf", &fr); a = fr * 1e14; ansa = 0; ansb = 1; ansd = fr; ll l = 1, r = 1e16; flag = true; while (l <= r) { ll mid = (l + r) / 2; ll p, q; sol(a - 1, 1e14, a + mid + 1, 1e14, p, q); if (p <= m && q <= n) { r = mid - 1; if (fabs(fabs(a / 1e14 - 1.0 * p / q) - ansd) < 1e-16 && (ansa != p || ansb != q)) { flag = false; } else if (fabs(a / 1e14 - 1.0 * p / q) < ansd) { ansa = p, ansb = q, ansd = fabs(a / 1e14 - 1.0 * p / q); flag = true; } } else { l = mid + 1; } } l = 1, r = 1e16; while (l <= r) { ll mid = (l + r) / 2; ll p, q; sol(max(a - mid - 1, 0), 1e14, a + 1, 1e14, p, q); if (p <= m && q <= n) { r = mid - 1; if (fabs(fabs(a / 1e14 - 1.0 * p / q) - ansd) < 1e-16 && (ansa != p || ansb != q)) { flag = false; } else if (fabs(a / 1e14 - 1.0 * p / q) < ansd) { ansa = p, ansb = q, ansd = fabs(a / 1e14 - 1.0 * p / q); flag = true; } } else { l = mid + 1; } } if (flag) io << ansa << '/' << ansb; else puts("TOO MANY"); return 0; } ``` ## 一个问题 我们一直在说让 $p, q$ 最小。那么,是否最小化 $p$ 等价于最小化 $q$ 呢? 是的。 证明: 若存在满足条件的最简分数 $\dfrac{x}{y}$,使得 $x>p, y < q$。那么 $\dfrac{x}{y} < \dfrac{x}{q} < \dfrac{p}{q}$。显然 $\dfrac{x}{q}$ 更优。 若存在满足条件的最简分数 $\dfrac{x}{y}$,使得 $x<p, y > q$。那么 $\dfrac{x}{y} < \dfrac{p}{y} < \dfrac{p}{q}$。显然 $\dfrac{p}{y}$ 更优。 因此最优解的分子和分母同时最小。 ## 双倍经验 [P5179](https://www.luogu.com.cn/problem/P5179)