問題
文字列 S と Q 個のクエリが与えられる。各クエリ $t_i$ に対し、$t_i$ が S の部分文字列として何回出現するかを答えよ。
制約
$1 \le |S| \le 2 \times 10^5$
$1 \le Q \le 10^5$
$\sum |t_i| \le 5 \times 10^5$
S, t_i は小文字英字
入出力例
入力例 1
abcabc
3
ab
bc
abc
出力例 1
2
2
2
ヒント (段階的開示)
ヒント1: 方向性
Suffix Automaton(SAM)は S の全部分文字列を O(|S|) で表現するオートマトン。各ノードが等価クラス。
ヒント2: アプローチ
1. SAM 構築 O(|S|)/2. cnt(出現回数)を suffix link 木で伝播/3. クエリを SAM 上でたどる。
ヒント3: 誘導
新ノード cur は cnt=1、clone ノードは cnt=0。len 降順で suffix link を辿って cnt を加算。
模範解答 (Python)
import sys
from collections import defaultdict
input = sys.stdin.readline
class SAM:
def __init__(self, n):
self.MAXN = 2 * n + 5
self.len = [0] * self.MAXN
self.link = [-1] * self.MAXN
self.trans = [dict() for _ in range(self.MAXN)]
self.cnt = [0] * self.MAXN
self.size = 1
self.last = 0
def extend(self, c):
if c in self.trans[self.last]:
q = self.trans[self.last][c]
if self.len[q] == self.len[self.last] + 1:
self.last = q
return
clone = self.size; self.size += 1
self.len[clone] = self.len[self.last] + 1
self.link[clone] = self.link[q]
self.trans[clone] = dict(self.trans[q])
self.cnt[clone] = 0
p = self.last
while p != -1 and self.trans[p].get(c) == q:
self.trans[p][c] = clone
p = self.link[p]
self.link[q] = clone
self.last = clone
return
cur = self.size; self.size += 1
self.len[cur] = self.len[self.last] + 1
self.cnt[cur] = 1
p = self.last
while p != -1 and c not in self.trans[p]:
self.trans[p][c] = cur
p = self.link[p]
if p == -1:
self.link[cur] = 0
else:
q = self.trans[p][c]
if self.len[q] == self.len[p] + 1:
self.link[cur] = q
else:
clone = self.size; self.size += 1
self.len[clone] = self.len[p] + 1
self.link[clone] = self.link[q]
self.trans[clone] = dict(self.trans[q])
self.cnt[clone] = 0
while p != -1 and self.trans[p].get(c) == q:
self.trans[p][c] = clone
p = self.link[p]
self.link[q] = clone
self.link[cur] = clone
self.last = cur
def solve():
S = input().strip()
n = len(S)
sam = SAM(n)
for c in S:
sam.extend(c)
order = sorted(range(sam.size), key=lambda x: -sam.len[x])
for v in order:
if sam.link[v] != -1:
sam.cnt[sam.link[v]] += sam.cnt[v]
Q = int(input())
for _ in range(Q):
t = input().strip()
cur = 0
ok = True
for c in t:
if c in sam.trans[cur]:
cur = sam.trans[cur][c]
else:
ok = False
break
print(sam.cnt[cur] if ok else 0)
solve()
Step-by-Step 解説
1SAM 構築
各文字を
各文字を
extend で追加。新ノード cur の cnt=1、clone は cnt=0。2出現回数の伝播
suffix link 木で子→親方向に cnt を加算(len 降順)。
suffix link 木で子→親方向に cnt を加算(len 降順)。
3クエリ処理
クエリ t を SAM 上でたどり、到達ノードの cnt が答え。遷移切れたら 0。
クエリ t を SAM 上でたどり、到達ノードの cnt が答え。遷移切れたら 0。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| clone の cnt を 1 | clone は複製 | cnt[clone] = 0 |
| 伝播方向が逆 | 親→子に加算 | 子→親(len 降順) |
| 初期ノード cnt | root は 0 | cnt[0] = 0 |
次のステップ
- 2文字列の最長共通部分文字列を SAM で
- Palindrome Automaton(PAM)