問題
$K$ 個の文字列すべてに共通する最長部分文字列の長さを求めよ。
制約
$1 \le K \le 10$
各 $|S_i| \le 10^4$
総長 $\le 10^5$
入出力例
入力例 1
3
abcabc
bcdef
bcabc出力例 1
2bc が全文字列に共通の最長。
ヒント (段階的開示)
ヒント1: 方向性
区切り文字で連結 → Suffix Array → LCP Array → スライディングウィンドウ。
ヒント2: アプローチ
各 suffix の所属文字列を
belong 配列で記録、ウィンドウ内に K 種類かつ LCP $\ge L$ を満たすか判定。ヒント3: 二分探索
「長さ $L$ 共通 substring 存在」を二分探索で。
模範解答 (Python)
import sys
from bisect import bisect_left
input = sys.stdin.readline
def build_suffix_array(s):
n = len(s)
sa = list(range(n))
rank = [ord(c) for c in s]
tmp = [0] * n
k = 1
while k < n:
def key(i):
return (rank[i], rank[i + k] if i + k < n else -1)
sa.sort(key=key)
tmp[sa[0]] = 0
for i in range(1, n):
tmp[sa[i]] = tmp[sa[i-1]]
if key(sa[i]) != key(sa[i-1]):
tmp[sa[i]] += 1
rank = tmp[:]
if rank[sa[-1]] == n - 1:
break
k *= 2
return sa
def build_lcp_array(s, sa):
n = len(s)
rank = [0] * n
for i, v in enumerate(sa):
rank[v] = i
lcp = [0] * n
h = 0
for i in range(n):
if rank[i] > 0:
j = sa[rank[i] - 1]
while i + h < n and j + h < n and s[i + h] == s[j + h]:
h += 1
lcp[rank[i]] = h
if h > 0:
h -= 1
return lcp
def solve():
K = int(input())
strings = [input().strip() for _ in range(K)]
sep_base = 1
T = ""
starts = []
for i, s in enumerate(strings):
starts.append(len(T))
T += s + chr(sep_base + i)
n = len(T)
sa = build_suffix_array(T)
lcp = build_lcp_array(T, sa)
def get_belong(pos):
idx = bisect_left(starts, pos + 1) - 1
return idx
belong_sa = [get_belong(sa[i]) for i in range(n)]
valid = [ord(T[sa[i]]) >= 97 for i in range(n)]
def check(L):
if L == 0:
return True
cnt = {}
lo = 0
for hi in range(n):
if not valid[hi]:
cnt = {}
lo = hi + 1
continue
b = belong_sa[hi]
cnt[b] = cnt.get(b, 0) + 1
while lo < hi:
if not valid[lo]:
lo += 1
cnt = {}
for j in range(lo, hi + 1):
if valid[j]:
cnt[belong_sa[j]] = cnt.get(belong_sa[j], 0) + 1
break
if lcp[lo + 1] < L:
b_lo = belong_sa[lo]
cnt[b_lo] -= 1
if cnt[b_lo] == 0:
del cnt[b_lo]
lo += 1
else:
break
if len(cnt) == K:
return True
return False
lo, hi = 0, min(len(s) for s in strings)
while lo < hi:
mid = (lo + hi + 1) // 2
if check(mid):
lo = mid
else:
hi = mid - 1
print(lo)
solve()
Step-by-Step 解説
1文字列連結
各文字列を chr(1), chr(2)... で区切って連結。
各文字列を chr(1), chr(2)... で区切って連結。
2SA + LCP
SA は $O(N \log^2 N)$、LCP は Kasai で $O(N)$。
SA は $O(N \log^2 N)$、LCP は Kasai で $O(N)$。
3スライディングウィンドウ
ウィンドウ内 LCP $\ge L$ かつ K 種類全部を含む。
ウィンドウ内 LCP $\ge L$ かつ K 種類全部を含む。
4二分探索
L について単調性。
L について単調性。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| セパレータ重複 | 文字列内に出現 | 小文字英字外を使用 |
| LCP インデックスズレ | 1-indexed か 0-indexed か | Kasai 実装の規約を確認 |
| セパレータ suffix 混入 | 除外漏れ | valid 配列でフィルタ |
| ウィンドウリセット忘れ | invalid でも跨ぐ | invalid で cnt を空に |
次のステップ
- K 個中 M 個以上に含まれる最長 substring
- Z-algorithm や Aho-Corasick との組合せ