Python奮闘記

主にPythonのことを書くつもりだったけど、プログラミング周り全般の備忘録ということにした。大体競プロ。

ABC129F Takahashi's Basics in Education and Learning

問題

F - Takahashi's Basics in Education and Learning

等差数列 s_nがある。これを文字通りに左から連結した数字の mod  M はいくつになるか。

気持ち

そのままだと扱いにくい。「連結」の操作は桁数さえ決まっていればなんとかなる。例えば桁数 cの数字 aを連結するなら、連結する前の答え Aに対し、

 \displaystyle
A\times 10^{c} +  a

が答えになる。

こんな感じで考えると、数列を桁数ごとに分けて考えるのは自然である。 桁数を固定するとグッと解きやすくなる。実際桁を cに固定したのち、オフセットを後でかけるとして、 r=10^{c} 、初項 a_0 、項 n とすると、

 \displaystyle
A_n = a_0 \times r^{n-1} + (a_0+B) \times r^{n-2} + \cdots + (a_0 + (n-1)B) \times r^0

を計算すれば良いことがわかる。これは例えば a_0 Bに項を分けると等比数列の性質を利用すればいい高校数学問題に帰着する。よし、解けた!

...と思うのだが、実はこれだとWAをいただいてしまう。この考えだと詰めが甘い。等比数列の公式はご存知の通り(?)

 \displaystyle
1 + r + \cdots r^{n-1} = \frac{1-r^{n}}{1-r}

となるので、 1-rで割る操作が必要になるのである。ところが今は法である M素数とは限らないため、逆元が存在しないかもしれない。この問題の邪悪ポイントである。

そうなると等比数列の公式は使うことができない。でも漸化式の決まった数列の要素を高速に求める手法を我々は知っている。そう、行列累乗である。 つまりさっきの A_nをうまいこと行列の形で書ければ...という気持ちになる。

ここまで行ければもう答えは出たようなものである。遷移はベクトル (A_n, a_n, 1)に対して考えれば良い。

ACコード

Submission #19568265 - AtCoder Beginner Contest 129

ちょっと汚いけど勘弁(ライブラリ省略)

import sys
input = sys.stdin.buffer.readline

def solve(L, A, B, mod):
    maxdig = len(str(A+B*(L-1)))
    alreadySelected = 1
    ans = 0
    for c in reversed(range(1, maxdig+1)):
        imax = min((10**c-A+B-1)//B-1, L-1)
        imin = max((10**(c-1)-A+B-1)//B, 0)

        a0 = A+B*imin
        d = B
        r = 10**c % mod
        n = imax - imin + 1
        if n <= 0: continue
        mat = [
            [r, 1, 0],
            [0, 1, d],
            [0, 0, 1]
        ]
        pmat = power_mat(mat, n, mod)
        score = a0 * pmat[0][1] + pmat[0][2]
        score = (score * alreadySelected) % mod
        ans = (ans + score) % mod
        alreadySelected = alreadySelected * pow(10, n*c, mod) % mod

    return ans

L, A, B, mod = map(int, input().rstrip().split())
print(solve(L, A, B, mod))

感想

modが素数じゃないの邪悪すぎる、と思ったがそれも含めてこの難易度なのかと思うと納得する。考察の段階でちゃんと細かいところまで詰めないといけない良い例。