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

あっとのTECH LOG

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

ABC138 E - Strings of Impurity

問題原文

atcoder.jp

問題要旨

文字列  S と文字列  T が与えられる。
 S 10^{100} 回繰り返した文字列において、何文字目までを使えば部分文字列として  T と同じものをとりうるか?   どう頑張っても取り得ない場合は -1 を出力せよ。

  •   1 \leq |S|, |T| \leq 10^{5}
  •  |S|, |T| は英子文字からなる。

解法

まず、  T にあって  S にないようなものがある場合、どう頑張っても不可能。
そうでない場合には、  S をとんでもない回数繰り返すので、どこかでは作ることができる。

 T は長くなったりしないのがポイントで、  T を前からなめながら「何文字目まで見たらいいか? 」を更新していく。
これは、あらかじめ  S の各文字の出現場所をメモっておいて、二分探索で目的の文字の次の出現場所を探すようにするとできる。
(例えば、 a を探すとして、 S 内の a の出現場所が[3, 6, 9]であって、今7文字目まで見ていることになっている場合、次の出現場所は9。すでに9文字目以降を見てしまっている場合には、もう一周しないと a は見つからないので、 S をもう一回繰り返した上で、3文字目まで見ていることにする。)

実装

注意点は2つ。

  1. bisect_left でなく、 bisect_right を使う。
    bisect_left だと、同じ文字が連続するような場所で、ある1つの出現場所を使いまわしてしまう。

  2. 最初の「どこまで見たか」の値は0より小さい値にする。
    0だと bisect_right を使った場合に最初の出現場所を飛ばしてしまうことがある。
    出現場所のindexの記録方法を1-indexedにすることでも解決できる。

あとサンプルだけなまじ通る場合があるので、  S = abab,  T = abbb などで実験すると吉。(これの答えは6になります)

from collections import defaultdict
from bisect import bisect_right
S = input()
T = input()
NS = len(S)

# 構成不可
if set(list(T)) - set(list(S)):
    print(-1)
    exit()


# 各文字の出現場所を記録
D = defaultdict(list)
for i, s in enumerate(S):
    D[s].append(i)


ans = 0
marker = -1
for t in T:
    target_char_appeared_indexes = D[t]
    bi = bisect_right(target_char_appeared_indexes, marker)
    # もう一周してこないと見つけられない
    if bi == len(target_char_appeared_indexes):
        ans += NS
        marker = target_char_appeared_indexes[0]
    else:
        marker = target_char_appeared_indexes[bi]


# 0-indexedのため+1
print(ans + marker + 1)

感想

あぁ〜〜〜〜〜〜〜こういう実装が瞬殺できると気持ちいいんじゃぁ〜〜〜〜〜〜〜〜〜〜^^(1WAの顔)
個人的に結構好きな問題の一つ。実装が心地いい。自分で強いサンプルを準備しようね!