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

あっとのTECH LOG

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

ABC149 D - Prediction and Restriction

問題原文

atcoder.jp

問題要旨

グーで勝ったら  R 点、チョキで勝ったら  S 点、 パーで勝ったら  P 点、という条件で、機械とじゃんけんをする。
あいこ、負けは0点とする。相手のこれから出す手が全て長さ  N の文字列  T としてわかっている時、最適に手を出すことで最高何点得られるか?
ただし、  K 回前に出した手と同じ手を出すことはできない

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

解法

modK T K 個のグループに分割できる。
分割後のグループで解くべき問題は、「最大何点取れるか?ただし直前と同じ手は出せない。」になる。これはDPとかで解ける。

実は貪欲でも解ける。modK でのグループ分割も実は必要ない。
簡単のために  K = 1 とした上で、  T = rrp とする。
p を2回連続で出せないため、 p 以外で、 そして3戦目に s を出せるような手(3戦目の勝ちを邪魔しない手)を出したい。
そしてこれを満たすような手が、毎回最低1つ存在することは楽に示せる。

「1戦目と2戦目のうちどっちで勝つべきか?」という疑問が浮かぶかもしれないが、上記の考え方を元にして  T=rrr などを想像すれば、「勝てる時に勝っておいて損しないこと」もわかる。これで貪欲の正当性が示せた(はず)。

まとめると、 - 勝てる時には勝っておく - そうでない時には、 K 回後に邪魔にならない手を出す。

以上のようにすることで最適な解が得られる。

実装

実装においては、 K 回後に邪魔にならないような手を求める必要はない。
最低1つ存在することはわかっているので、 「何も手を出さず、得点は0」ということにして処理をすればいい。

N, K = map(int, input().split())
R, S, P = map(int, input().split())
T = list(input())


ans = 0
commands = [''] * N
for i, t in enumerate(T):
    if t == 'r':
        command = 'p'
        point = P

    elif t == 's':
        command = 'r'
        point = R

    elif t == 'p':
        command = 's'
        point = S

    if (i - K >= 0) and (commands[i -  K] == command):
        command = ''
        point = 0

    ans += point
    commands[i] = command

print(ans)

感想

今回は使わなかったけど、 「modK で問題の簡単化・グループ化」という発想はすぐに取り出せるようにしておきたい。
ある操作をしても損することは絶対ないかも? という発想も!