B - 合言葉の確認 / Password Verification 解説 by admin
Claude 4.6 Opus (Thinking)概要
各文字列が sayounara を部分列として含むかどうかを判定する問題です。部分列とは、元の文字列から文字を飛び飛びに(順序を保って)取り出して作れる文字列のことです。
考察
部分列の判定とは
部分列の判定は、「ターゲット文字列の各文字を、元の文字列の中で左から順に見つけられるか?」という問題に帰着できます。
例えば、文字列 sxxxaxyzoounara に対して sayounara が部分列として含まれるか確認するには:
sを探す → 1文字目のsが一致 ✓aを探す → 5文字目のaが一致 ✓yを探す → 7文字目のyが一致 ✓oを探す → 9文字目のoが一致 ✓- … 以下同様に最後の
aまで全て見つかればYes
このように、ターゲット文字列の先頭から1文字ずつ順番に、元の文字列を左から走査して一致する文字を探す貪欲法(グリーディ)が最適です。
なぜ貪欲法で正しいのか
ターゲットの各文字をできるだけ早い位置でマッチさせることで、残りの文字列に余裕を持たせることができます。これにより、マッチの機会を逃すことがありません。
素朴なアプローチとの比較
「部分列」と聞くと、全ての取り出し方を列挙したくなるかもしれませんが、それは指数的な計算量になり TLE になります。上記の貪欲法なら、元の文字列を1回走査するだけで判定できます。
アルゴリズム
各文字列 \(S_i\) に対して、以下の手順で判定します:
- ターゲット
target = "sayounara"のポインタ \(j\) を \(0\) に初期化する。 - \(S_i\) の各文字 \(c\) を左から順に見る。
- もし \(c\) が
target[j]と一致すれば、\(j\) を \(1\) 進める。 - \(j\) が
targetの長さ(\(9\))に達したら、部分列として含まれているのでYes。
- もし \(c\) が
- \(S_i\) を最後まで見ても \(j\) が \(9\) に達しなければ
No。
計算量
- 時間計算量: \(O\left(\displaystyle\sum_{i=1}^{N} |S_i|\right)\)
- 各文字列を1回走査するだけなので、全文字列の長さの合計に比例します。制約より合計は最大 \(5 \times 10^6\) なので十分高速です。
- 空間計算量: \(O\left(\max |S_i|\right)\)
- 各文字列を読み込む分の領域が必要です。出力バッファを含めても \(O(N + \max |S_i|)\) です。
実装のポイント
高速な入力: 文字列の合計長が最大 \(5 \times 10^6\) と大きいため、Python では
sys.stdin.readlineを使って入力を高速化しています。標準のinput()では TLE になる可能性があります。出力のまとめ書き: 毎回
printするのではなく、結果をリストに溜めて最後に'\n'.join(out)で一括出力することで、I/O のオーバーヘッドを削減しています。rstrip()の使用:readline()は末尾に改行文字を含むため、.rstrip()で除去してから判定を行います。これを忘れると、改行文字がマッチ処理に影響する可能性があります。早期終了: ターゲット文字列の全文字がマッチした時点(\(j = 9\))で即座に
Trueを返し、残りの文字列の走査を省略しています。ソースコード
import sys
input = sys.stdin.readline
def check(s):
target = "sayounara"
j = 0
for c in s:
if c == target[j]:
j += 1
if j == len(target):
return True
return False
N = int(input())
out = []
for _ in range(N):
s = input().rstrip()
out.append("Yes" if check(s) else "No")
print('\n'.join(out))
この解説は claude4.6opus-thinking によって生成されました。
投稿日時:
最終更新: