問題
長さ
-
$\mathrm{KMP}[i] = $ ( 文字列$S[:i]$ の接頭辞と接尾辞が最大何文字一致しているか)
を
S : aabaabaaa
KMP: _010123452 (_=-1)
解説
- https://snuke.hatenablog.com/entry/2014/12/01/235807
- http://potetisensei.hatenablog.com/entry/2017/07/10/174908
使い道
- 最小の周期長を求める
- 「文字列
$S$ の最小の周期長」=「$k$ 文字ずらしたものが元の文字列と一致するような最小の$k ~(k>0)$ 」- 例えば
ababaの最小の周期長は2ababa __ababa
- 例えば
- 1-indexedで
i-KMP[i]- 具体例
S : aabaabaaa KMP: _010123452 (_=-1) mpl: 113333337
- 具体例
- 「文字列
- 文字列検索
- 文字列
Sの中にTが含まれるか検索したい時には、kmp(T+'#'+S)をすると良い
- 文字列
def kmp(s):
n = len(s)
kmp = [0] * (n+1)
kmp[0] = -1
j = -1
for i in range(n):
while j >= 0 and s[i] != s[j]:
j = kmp[j]
j += 1
kmp[i+1] = j
return kmp回文関連のやつ
長さ
-
$Z[i] = $ ($S$ と$S[i:]$ の最長共通接頭辞(LCP)の長さ)
を
S:aaabaaaab
Z:921034210
使い道
- LCP
- 文字列検索
- 文字列
Sの中にTが含まれるか検索したい時には、z_algorithm(T+'#'+S)をすると良い
- 文字列
解説
def z_algorithm(s):
n = len(s)
z = [0] * n
z[0] = n
i = 1
lcp = 0
while i < n:
while i+lcp < n and s[i+lcp] == s[lcp]:
lcp += 1
z[i] = lcp
if lcp == 0:
i += 1
continue
k = 1
while i+k < n and k+z[k] < lcp:
z[i+k] = z[k]
k += 1
i += k
lcp -= k
return z問題
- https://odan3240.hatenablog.com/entry/2015/02/16/111938
- https://ei1333.github.io/luzhiled/snippets/string/rolling-hash.html
- https://scrapbox.io/pocala-kyopro/ローリングハッシュ
- http://perogram.hateblo.jp/entry/rolling_hash
- アリ本 第2版 「4-7 文字列を華麗に扱う」
そもそもハッシュ関数とは、文字列・数列といった何かしらのデータが与えられた時に、そのデータに対応したなんらかの数値を得るなんらかの操作(関数)のこと。 データをハッシュ関数にかけて得られた数値のことをハッシュ値という。 元データの比較処理が重いような場合(文字列・数列など)、ハッシュ値に変換してから比較することで高速化が望めたりする。
ただし、ハッシュ値の計算に時間がかかっていては意味がない。
そこで数列に対するハッシュ関数として、高速で便利なのがローリングハッシュである。
ローリングハッシュ(多項式ハッシュ, Karp-Rabin Fingerprint)とは、数列
と定義されるハッシュ関数のこと。文字列にも適用可能。( 文字列
多項式の形なので、(累積和のようなイメージで)効率的に計算できる のがミソ。具体的には、長さ
直接文字列を比較するのではなく、効率的に計算されるハッシュ値の比較をすることで処理の高速化を図る。
ハッシュは衝突することもある。
複数の
class RollingHash:
def __init__(self, s: str, base: int=1007, mod: int=10**9+7):
self.s = s
self.n = len(s)
self.base = base
self.mod = mod
# preprocess
self.hash_cum = [0] * (self.n + 1) # hash_cum[i] = (hash of s[:i])
self.base_pow = [1] * (self.n + 1) # base_pow[i] = base ** i
for i in range(self.n):
self.hash_cum[i+1] = (self.hash_cum[i] * base + ord(s[i])) % mod
self.base_pow[i+1] = (self.base_pow[i] * base) % mod
def get_hash(self, l: int, r: int) -> int: # get hash value of the substring: s[l:r]
hash_val = self.hash_cum[r] - self.hash_cum[l] * self.base_pow[r-l] % self.mod
if hash_val < 0:
hash_val += self.mod
return hash_val
class RollingHashMulti:
def __init__(self, s: str, base_list: list=[1007, 2009], mod_list: list=[10**9+7, 10**9+9]):
self.n = len(base_list)
self.base_list = base_list
self.mod_list = mod_list
self.rh_list = [RollingHash(s, base_list[i], mod_list[i]) for i in range(self.n)]
def get_hash(self, l: int, r: int) -> tuple:
return tuple( self.rh_list[i].get_hash(l, r) for i in range(self.n) )class RollingHash2D:
def __init__(self, s, n, m, b=1007, c=2009, mod=10**9 + 7):
self.s = s
self.n = n
self.m = m
self.b = b
self.c = c
self.mod = mod
# preprocess
self.hash_cs = [[0] * (self.m + 1) for _ in range(self.n + 1)]
self.b_pow = [1] * (self.n + 1) # base_pow[i] = base ** i
self.c_pow = [1] * (self.m + 1) # base_pow[i] = base ** i
for i in range(self.n):
self.b_pow[i + 1] = (self.b_pow[i] * self.b) % mod
for i in range(self.m):
self.c_pow[i + 1] = (self.c_pow[i] * self.c) % mod
for i in range(self.n):
for j in range(self.m):
self.hash_cs[i + 1][j + 1] = \
self.hash_cs[i + 1][j] * self.c \
+ self.hash_cs[i][j + 1] * self.b \
- self.hash_cs[i][j] * self.b * self.c \
+ self.s[i][j]
self.hash_cs[i + 1][j + 1] %= self.mod
def calc_rolling_hash(self, il: int, ir: int, jl: int, jr: int) -> int:
hash_val = \
self.hash_cs[ir][jr] \
- self.hash_cs[ir][jl] * self.c_pow[jr-jl] \
- self.hash_cs[il][jr] * self.b_pow[ir-il] \
+ self.hash_cs[il][jl] * self.b_pow[ir-il] * self.c_pow[jr-jl]
hash_val %= self.mod
return hash_val
def calc_hash(self, t, nt, mt):
h = 0
for i in range(nt):
for j in range(mt):
h += t[i][j] * self.b_pow[nt - i - 1] * self.c_pow[mt - j - 1]
h %= self.mod
return hToDo
RollingHashBaseという基底クラスを作って、RollingHashStrとRollingHashStrMultiとRollingHash2dimをそれを継承したクラスとして実現する
問題
- ABC054 B - Template Matching (200点)
- 二次元のRollingHashでも解ける
- ABC141 E - Who Says a Pun? (500点)
- RollingHashと二分探索
- ABC150 F - Xor Shift (600点)
- 数列の比較をRollingHashで行う問題