【競プロ】過去問精選100問「AtCoder Regular Contest 054 B - ムーアの法則」解法
Free-PhotosによるPixabayからの画像

このシリーズではレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】でまとめられている100問をPythonで解いています。

問題

AtCoder Regular Contest 054 B - ムーアの法則

ポイント

x年後のコンピュータでT(334)を解くのにかかる時間は

  f(x) = x + P / 2 ^ (x/1.5) 

と表すことができ、グラフは下記のような凸関数になります。(P=3.000の例)

求めたい値はこのグラフで1番小さい値、すなわち頂点です。

頂点を求めるには三分探索という方法を使います。二分探索と基本的な考え方は同じです。

left, rightに対して三等分する点c1, c2を考えます。

上記グラフにおいて、

 ①f(c1) < f(c2)であれば、頂点はleftからc2の間に存在することがわかります。

 ②f(c1) > f(c2) であれば、 頂点はc1からrightの間に存在することがわかります。

このようにして徐々に探索の幅を狭めていきます。

(①の例ではc2を次のrightに、②の例ではc1を次のleftに設定します。 f(c1) = f(c2) の場合は①か②どちらか好きな方に含めてください。※<= または >= とする)

尚、三分探索では数式を直接解いて答えを導いている訳ではないので、得られる解はあくまで近似値となります。よって、三分探索を十分な回数(left-rightの幅の大きさが十分小さくなるまで)行うことによって解の精度を高めます。この問題においては誤差を10 ^ -8 以下に抑えます。

コード

def main():
    import sys
    sys.setrecursionlimit(10 ** 6)
    input = sys.stdin.readline

    P = float(input())

    # 三分探索を行うための関数
    def ternary_search(left: int, right: int):
        while abs(left - right) > 1e-10:  # leftとrightの差(探索の幅)が十分小さくなるまで行う
            c1 = (2 * left + right) / 3
            c2 = (left + 2 * right) / 3
            if cal(c1) >= cal(c2):
                left = c1
            else:
                right = c2

        return (left + right) / 2  # 最終的にleftとrightの中間地点を頂点の近似解として返す

    # x年後のコンピュータでT(334)を解くのにかかる時間を計算する関数
    def cal(x: int):
        return x + P / (2 ** (x / 1.5))

    # 最初のleftの値は0(本問題ではx<0の場合は考えないので)、rightの値はleft-rightの範囲に頂点が含まれるように十分大きな値にする。
    print(cal(ternary_search(0, 1000)))


if __name__ == '__main__':
    main()

おすすめの記事