AT274の技術だったりなかったり

あっとのTECH LOG

競プロのこととか技術のこととか。たまに日常を書きます。

JOI難易度6 スタンプラリー 2

問題原文

atcoder.jp

問題要旨

長さ  NJ, O, I からなる文字列  S が与えられる。
文字列のある1箇所に J, O, I の文字を差し込むことで、変化後の文字列 に含まれる JOI という部分文字列のとりかたの通り数を最大化せよ。

  •   1 \leq N \leq 10^{5}

解法

基本はdp。 i 文字目までみたときの Jの数, JO の数, JOI の数 を数えながら遷移すればOK。

この問題特有の、「1文字差し込む」という操作については、
J S の先頭につけて損しない。 → J +  S についてdp。
I S の末尾につけて損しない。 →  S + I についてdp。
O は差し込んだ場所について、(左側にある J の数)× (右側にある I)の数だけ通り数が増える。
 S についてのdpの計算結果に付け加える。J の数と I の数は累積和をとっておけば高速に求められるので、差込場所を全探索できる。

でOK!

実装

制約やさしいので、辞書でdpまわしても余裕でまにあう。
直感的になってすき。

from itertools import accumulate
N = int(input())
S = input()


def calc(X):
    dp = {'J': 0, 'JO': 0, 'JOI': 0}
    for x in X:
        if x == 'J':
            dp['J'] += 1

        elif x == 'O':
            dp['JO'] += dp['J']

        elif x == 'I':
            dp['JOI'] += dp['JO']

    return dp['JOI']


ans1 = calc('J' + S)
ans2 = calc(S + 'I')
ans3 = calc(S)

J_cnt = list(accumulate([int(s == 'J') for s in S]))
I_cnt = list(accumulate([int(s == 'I') for s in S]))
ans3_add = 0
for i in range(N):
    ans3_add = max(ans3_add, J_cnt[i] * (I_cnt[-1] - I_cnt[i]))

print(max(ans1, ans2, ans3 + ans3_add))

感想

We Love ABC DPってこころの中で言ってる。(たぶんあっちのほうがむずかしい)

atcoder.jp