Stern-Brocot 木がちょうどよく使える
問題概要
正の整数 が与えられる。分子・分母がともに 以下の正の整数であって、既約分数であるような分数の集合を と表すことにする。
- を満たす最大の の要素
- を満たす最小の の要素
をそれぞれ求めよ。
制約
- (解の存在が保証される)
考えたこと
単純な解法としては、分母を固定して分子の値を二分探索する方法が考えられる。それで十分通せる。
しかし、Stern-Brocot 木を知っていればかなり楽に通せる。Stern-Brocot 木とは、下図 (ここ引用) のように
- すべての既約分数がちょうど一度ずつ現れる
- 扱う既約分数は単調増加である
- より深い既約分数は、分子または分母の値がより大きい
という特徴がある。今回の問題にはピッタリである。
通常はこんな感じの実装でできる。
void SternBrocot(long long N, long long sl = 0, long long bl = 1, long long sr = 1, long long br = 0) { long long s = sl + sr, b = bl + br; if (s > N || b > N) return; // left SternBrocot(N, sl, bl, s, b); // consider s/b (monotone increasing) { } // right SternBrocot(N, s, b, sr, br); }
ただし今回は高速化のため
- のときは、左側の探索を枝刈りする
- は単調増加で登場するので、初めて を超えた時点で探索を打ち切る
という工夫を実施した。
コード
#include <bits/stdc++.h> using namespace std; long long P; long long x, y, u, v; bool exist_bigger = false; // consider s/b void SternBrocot(long long N, long long sl = 0, long long bl = 1, long long sr = 1, long long br = 0) { long long s = sl + sr, b = bl + br; if (s > N || b > N) return; // left (only when bigger case) if (s*s > b*b*P) SternBrocot(N, sl, bl, s, b); // consider s/b (monotone increasing) { if (s*s < b*b*P) u = s, v = b; else { if (!exist_bigger) x = s, y = b, exist_bigger = true; return; } } // right SternBrocot(N, s, b, sr, br); } int main() { long long N; while (cin >> P >> N, P) { exist_bigger = false; SternBrocot(N); cout << x << "/" << y << " " << u << "/" << v << endl; } }