Day 029-Q5 — 多項式ハッシュ + Z-algorithm(最長共通部分文字列)

2026-05-12 赤色 Master / Phase 8+ ★★★★★★★★★ 文字列ハッシング

問題

文字列 $S$(長さ $N$)に対し、UPDATE と LCS(最長共通部分文字列の長さ)クエリを処理せよ。

制約

$1 \le N \le 2 \times 10^4$
$1 \le Q \le 2 \times 10^3$
小文字英字

入出力例

入力例 1

8 3
abababab
LCS 0 3 4 7
UPDATE 0 1 x
LCS 0 3 4 7

出力例 1

4
2

ヒント (段階的開示)

ヒント1: 方向性
「長さ $k$ の共通 substring が存在するか」の判定を二分探索 + ローリングハッシュで。
ヒント2: アプローチ
UPDATE は直接更新 $O(N)$。LCS は二分探索 $O(\log N)$ × ハッシュ計算 $O(N)$。
ヒント3: ダブルハッシュ
2 つのハッシュで衝突確率を $\approx 10^{-18}$ に下げる。

模範解答 (Python)

import sys
input = sys.stdin.readline

MOD1, BASE1 = (1 << 61) - 1, 131
MOD2, BASE2 = 10**9+7, 137

def rolling_hash_set(s, k, mod, base):
    n = len(s)
    if k > n or k == 0:
        return set()
    h = 0
    pw = 1
    for i in range(k):
        h = (h * base + ord(s[i])) % mod
    if k > 1:
        for _ in range(k - 1):
            pw = pw * base % mod
    result = {h}
    for i in range(1, n - k + 1):
        h = (h - ord(s[i-1]) * pw % mod + mod) % mod
        h = (h * base + ord(s[i + k - 1])) % mod
        result.add(h)
    return result

def has_common_of_len(s1, s2, k):
    h1a = rolling_hash_set(s1, k, MOD1, BASE1)
    h2a = rolling_hash_set(s2, k, MOD1, BASE1)
    common_a = h1a & h2a
    if not common_a:
        return False
    return True

def lcs_len(s1, s2):
    if not s1 or not s2:
        return 0
    lo, hi = 0, min(len(s1), len(s2))
    while lo < hi:
        mid = (lo + hi + 1) // 2
        if has_common_of_len(s1, s2, mid):
            lo = mid
        else:
            hi = mid - 1
    return lo

def solve():
    N, Q = map(int, input().split())
    S = list(input().strip())
    out = []
    for _ in range(Q):
        line = input().split()
        if line[0] == 'UPDATE':
            l, r, c = int(line[1]), int(line[2]), line[3]
            for i in range(l, r+1):
                S[i] = c
        else:
            l1, r1, l2, r2 = int(line[1]), int(line[2]), int(line[3]), int(line[4])
            s1 = ''.join(S[l1:r1+1])
            s2 = ''.join(S[l2:r2+1])
            out.append(str(lcs_len(s1, s2)))
    print('\n'.join(out))

solve()

Step-by-Step 解説

1二分探索に帰着
「長さ $k$ の共通 substring が存在」は単調なので二分探索可。
2ローリングハッシュ
長さ $k$ の全 substring ハッシュを $O(N)$ で列挙、スライディングウィンドウ。
3ダブルハッシュ
2 つの異なる mod, base で衝突回避。
4UPDATE
制約小さいので $O(N)$ で直接更新。

よくあるミス

ミス原因正しい書き方
ハッシュスライドで負数引き算でマイナス(h - ... + mod) % mod
べき乗の初期化pw の値ずれpw = base^(k-1) % mod
LCS と LCS substring を混同別アルゴリズムsubstring は二分探索+ハッシュ
k=0 の処理空 substring先頭で return set()

次のステップ

  • 動的挿入・削除を伴う LCS substring → Suffix Automaton

自己評価

自分の回答

気づき・メモ