問題
$N$ 個のパターン文字列と $Q$ 個のテキストに対し、各テキスト中のパターン種類数と延べ出現回数を求めよ。
制約
$1 \le N, Q \le 1000$
$|P_i| \le 100$
$|T_j| \le 10^5$
パターン総長 $\le 10^5$
入出力例
入力例 1
3 2
ab
bc
abc
abcbc
xyzab出力例 1
3 4
1 1abcbc: ab(1), bc(2), abc(1) → 種類3, 延べ4。xyzab: ab(1) のみ。
ヒント (段階的開示)
ヒント1: 方向性
Aho-Corasick オートマトン: Trie + Failure Link + Dictionary Link。
ヒント2: アプローチ
パターンで Trie 構築 → BFS で fail 設定 → output を fail 経由で伝播 → テキスト走査でマッチ収集。
ヒント3: 誘導
fail[s] = s に対応する文字列の最長真の接尾辞でオートマトン上にあるノード。KMP の多パターン版。
模範解答 (Python)
import sys
from collections import deque
input = sys.stdin.readline
class AhoCorasick:
def __init__(self):
self.goto = [{}]
self.fail = [0]
self.output = [[]]
def add_pattern(self, pattern, idx):
state = 0
for c in pattern:
if c not in self.goto[state]:
self.goto[state][c] = len(self.goto)
self.goto.append({})
self.fail.append(0)
self.output.append([])
state = self.goto[state][c]
self.output[state].append(idx)
def build(self):
q = deque()
for c, s in self.goto[0].items():
q.append(s)
while q:
r = q.popleft()
for c, s in self.goto[r].items():
q.append(s)
state = self.fail[r]
while state != 0 and c not in self.goto[state]:
state = self.fail[state]
f = self.goto[state].get(c, 0)
if f == s:
f = 0
self.fail[s] = f
self.output[s] = list(set(self.output[s]) | set(self.output[f]))
def search(self, text):
state = 0
results = []
for c in text:
while state != 0 and c not in self.goto[state]:
state = self.fail[state]
state = self.goto[state].get(c, 0)
for pat_idx in self.output[state]:
results.append(pat_idx)
return results
def main():
N, Q = map(int, input().split())
patterns = [input().strip() for _ in range(N)]
texts = [input().strip() for _ in range(Q)]
ac = AhoCorasick()
for i, p in enumerate(patterns):
ac.add_pattern(p, i)
ac.build()
for text in texts:
hits = ac.search(text)
kinds = len(set(hits))
total = len(hits)
print(kinds, total)
main()
Step-by-Step 解説
1Trie 構築
パターン文字列を挿入、終端ノードにインデックス記録。
パターン文字列を挿入、終端ノードにインデックス記録。
2Failure Link を BFS で構築
KMP の失敗関数の多パターン拡張。根から順に計算。
KMP の失敗関数の多パターン拡張。根から順に計算。
3output 伝播
fail を辿ったマッチを事前マージで検索効率化。
fail を辿ったマッチを事前マージで検索効率化。
4テキスト走査
1 文字ずつ遷移、失敗時は fail で巻き戻し。
1 文字ずつ遷移、失敗時は fail で巻き戻し。
5集計
set(hits) で種類数、len(hits) で延べ回数。よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
fail[s] == s | ルート遷移なし | if f == s: f = 0 |
| output マージ忘れ | fail 先の出力反映なし | output[s] |= output[fail[s]] |
| failure 遷移ループ | 状態巻き戻しが浅い | while で繰り返し巻き戻し |
| パターン重複カウント | 同パターンの複数終端 | set() で種類数管理 |
次のステップ
- 出現位置の列挙
- 動的追加・削除(Incremental AC)