【競プロ典型90問】「058 - Original Calculator(★4)」解法

問題

058 - Original Calculator(★4)

ポイント

ボタンAを押した場合の処理は関数で用意するとして、愚直にK回その処理を行うとTLEとなってしまいます。

そこで、ボタンAの処理を何回か行った際、1度出てきた数字に戻ってきた場合を考えます。
図にするとこんな感じです。※青丸がボタンAを押した後の数字、ピンクがAを押した回数

こうなった場合、その後何回ボタンAを押そうとも数字の遷移は2→7→9→1→3となります。
このことを利用して、実際にボタンAの処理を行う回数を減らしたいと思います。

具体的には、ボタンを押す回数Kを
 K = (K - ループが起こるまでボタンを押す回数)÷ ループの大きさ + ループが起こるまでボタンを押す回数
に置き換えます。

上の例では
 K = (K - 2)÷ 5 + 2 となります。

このようにすることで、実際にボタンAの処理を行う回数を減らし、TLEを回避します。

尚、ループが全く起こらない巨大なKが問題として与えられた場合はどうしようもないのですが、
そこは出題者側で調整されているのか、数学的にループが必ず起こることが証明できるのかは
私も分かっていません…。誰か教えてください。。

以上を踏まえ、実装したコードがこちらです。

コード

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

    def A(x):
        return (x + sum(map(int, list(str(x))))) % 100000

    N, K = map(int, input().split())
    done = [0] * (10**5)

    cnt = 0  # ループが起こるまでボタンを押す回数
    tmp = N
    while True:
        cnt += 1
        tmp = A(tmp)
        if done[tmp] != 0:  # 同じ数字に戻ってきたらループの大きさを記録
            loop_size = cnt - done[tmp]
            break
        else:
            done[tmp] = cnt

    # Kを置き換える
    if K > cnt:  # Kがcnt以下の場合はそもそもloopが発生しない
        K = (K-cnt) % loop_size + cnt

    for _ in range(K):
        N = A(N)

    print(N)


if __name__ == '__main__':
    main()
おすすめの記事