[Library Checker] Rational Approximation

スポンサーリンク

問題概要

問題文 →
自分の提出 → (35 ms)

一発書きで fastest (2024/08/27)になったのは意外でした.

解法

以下,有理数と言えば非負であることを仮定し,有理数 a/b のような表記はすべて a,b は非負整数で gcd(a,b)=1 を満たすことを仮定します.また便宜上 1/0 という有理数も考え,これらは任意の非負有理数より大きな有理数()であると考えます.

本問題は形式上は「x/y を近似する」という形になっていますが,より一般に次の形に一般化して解くことができますし,実用上も役に立つと思います.

有理数を引数として bool 値を返す関数 check(a/b) があり,次の意味で単調性があるとする:T={x=a/bcheck(a/b)=true}, T={x=a/bcheck(a/b)=false} とするとき,0/1T, 1/0F かつ任意の有理数 x,y に対して xT,yF ならば x<y
このとき有理数集合 S={a/ba,bN} は有限集合で,0/1ST および 1/0SF であるから max(ST) および min(SF) が存在する.これらを求めよ.

計算量解析において,check(a/b) の呼び出しは O(1) 時間であると仮定します.

まず Stern-Brocot 木をたどれば,次のようなアルゴリズムが考えられます:

  • x1=a1/b1=0/1, x2=a2/b2=1/0 で初期化する.
  • max(a1+a2,b1+b2)N である限り次を行う:
    • check(a1+a2,b1+b2)=true ならば x1=a1/b1(a1+a2)/(b1+b2) に取り換える.
    • check(a1+a2,b1+b2)=false ならば x2=a2/b2(a1+a2)/(b1+b2) に取り換える.

このとき x1=max(ST) および x2=min(SF) が出力されます.Stern-Brocot 木の性質より,常に次の条件が保たれるからです.

  • x1T,x2F であり,x1<x<x2 を満たす有理数 x=a/b のうちで max(a,b) が最小のものは (a1+a2)/(b1+b2) である.

しかしこのアルゴリズムは最悪ケースで Θ(N) 時間かかります.


このアルゴリズムを,一方に加算する反復をひとまとめににします.

  • max(a1+a2,b1+b2)N である限り次を行う:
    • check(a1+ka2,b1+kb2)=true かつ a1+ka2N,b1+kb2N となる最大の k をとり,x1=a1/b1(a1+ka2)/(b1+kb2) に取り換える.
    • check(ka1+a2,kb1+b2)=false かつ ka1+a2N,b1+kb2N となる最大の k をとり,x2=a2/b2(ka1+a2)/(kb1+b2) に取り換える.

このような k を選択するステップは Θ(logN) 回です(常に k=1 が選ばれる場合がワーストケースで,その場合の分子・分母はフィボナッチ数列の増大度です).

本問「Rational Approximation」の場合はこのような「最大の k」が O(1) 時間で計算できるため,これで Θ(logN) 時間の解法となります.


「最大の k」を check 関数呼び出しだけで求めることを考えましょう.例えば 0kN の範囲を二分探索するという方法が考えられますが,それではアルゴリズム全体で最悪 Θ(log2N) 時間の計算量になってしまいます.

二分探索を指数探索に変えるだけで,アルゴリズム全体が Θ(logN) 時間になります.

つまり,k=2i が足せなくなるまで i をインクリメントし,足せなくなったら i をデクリメントしながら貪欲に 2i を足していきます.

a1/b1 に対する i のインクリメントの回数,a2/b2 に対する i のインクリメント回数をそれぞれ評価すれば,アルゴリズム全体が Θ(logN) 時間になることは容易に分かります.

タイトルとURLをコピーしました