import random """ローリングハッシュ 文字列にハッシュをかけて1次元の数に置き換えます。 長い文字列でも、一致判定をO(1)くらいでできます。 ・使い方 rh=RollingHash(S): インスタンス作成。 計算量: O(len(S)) rh.value(l,r): 半開区間[l,r)のハッシュ値を返します。 計算量: O(1) rh.merged_string_value(section_list): 半開区間をたくさん入れたリストsection_listについて、 各区間の文字列を連結させてできる文字列のハッシュ値を返します。 計算量: O(len(section_list)) (そんなにやばくない) rh.same(section_list1,section_list2): 2つの文字列が同じならTrueを、そうでないならFalseを返します。 計算量: O(len(section_list1)+len(section_list2)) (そんなにやばくない) ・使わせてもらったもの ミラーラビン素数判定法【https://qiita.com/srtk86/items/609737d50c9ef5f5dc59】 """ def is_prime(n): if n == 2: return True if n == 1 or n & 1 == 0: return False d = (n - 1) >> 1 while d & 1 == 0: d >>= 1 for k in range(100): a = random.randint(1, n - 1) t = d y = pow(a, t, n) while t != n - 1 and y != 1 and y != n - 1: y = (y * y) % n t <<= 1 if y != n - 1 and t & 1 == 0: return False return True # 区間[left,right)内の素数を1つランダムに出力します。 def prime_generator(left, right): while True: ret_prime = random.randrange(left, right) if is_prime(ret_prime): return ret_prime class RollingHashConst: hash_dic = dict() rhMOD = prime_generator(1 << 32, 1 << 33) cf = random.randrange(1 << 22, 1 << 23) rui_cf2 = [1] def regist_element(self, el): rd = random.randrange(1 << 22, 1 << 23) self.hash_dic[el] = rd def hashed_value(self, el): if el not in self.hash_dic: self.regist_element(el) return self.hash_dic[el] def get_rui_cf(self, length): if len(self.rui_cf2) < length: self.make_rui_cf(length) return self.rui_cf2 def make_rui_cf(self, length): need_add_cnt = length - len(self.rui_cf2) for _ in range(need_add_cnt): new_val = self.rui_cf2[-1] * self.cf % self.rhMOD self.rui_cf2.append(new_val) class RollingHash(RollingHashConst): def __init__(self, init_S): self.n = len(init_S) self.hashed_list = [0] val = 0 for s in init_S: val *= self.cf val += self.hashed_value(s) val %= self.rhMOD self.hashed_list.append(val) self.rui_cf = self.get_rui_cf(length=self.n + 1) def value(self, l, r): assert 0 <= l < r <= self.n, f'0<=l