字符串 H a s h Hash H a s h
Hash 是一种将一个字符串映射为一个数字的算法,它可以帮助我们在 O ( 1 ) O(1) O ( 1 ) 的时间内判断两个字符串是否相等,或进行回文判断。但 H a s h Hash H a s h 也有一定的出错概率。当数据范围为 1 0 5 10^5 1 0 5 级别时可能会出现哈希冲突,可以使用双模数哈希。
基础 h a s h hash ha s h
时间复杂度:O ( 1 ) O(1) O ( 1 )
对于一个长度为 n n n 的字符串,它的哈希值为 ∑ i = 1 i ≤ n s i × b a s e n − i m o d P \sum_{i=1}^{i\le n} s_i\times base^{n-i} \bmod P ∑ i = 1 i ≤ n s i × ba s e n − i mod P 。其中 b a s e base ba se 和 P P P 自选。
具体实现:
对于一个字符串 S S S ,它的每一个位前缀哈希值就等于上一位前缀哈希值 H i − 1 × b a s e + s i H_{i-1}\times base +s_i H i − 1 × ba se + s i 。
预处理出 b a s e base ba se 的 1 ∼ n 1\sim n 1 ∼ n 次方 P o w 1 ∼ n Pow_{1\sim n} P o w 1 ∼ n
对于一段区间 [ l , r ] [l,r] [ l , r ] ,它的 h a s h hash ha s h 值即为 H r − H l − 1 × P o w r − l + 1 H_r-H_{l-1}\times Pow_{r-l+1} H r − H l − 1 × P o w r − l + 1
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 unsigned long long h[1e5 + 10 ], pow[1e5 + 10 ];bool check (int l, int r) { return h[r] - h[l - 1 ] * pow[r - l + 1 ]; } void init_hash (char * s) { int n = strlen (s); for (int i = 1 ; i <= n; ++i) { h[i] = h[i - 1 ] * base + s[i]; } } void Init_pow () { pow[0 ] = 1 ; for (int i = 1 ; i <= Maxn; ++i) pow[i] = pow[i - 1 ] * base; }
允许失配 k 次的匹配
时间复杂度:O ( n k l o g n ) O(nklogn) O ( nk l o g n )
具体实现:
枚举子串,每次二分+hash 找到第一个不同的位置,从这个位置之后继续匹配。
代码实现:暂无
K M P KMP K MP 模式串匹配
时间复杂度:O ( n ) O(n) O ( n )
K M P KMP K MP 算法是一个在线性时间内完成字符串匹配的算法。
具体实现:
n x t nxt n x t 数组维护在文本串每一位最长的前缀等于后缀的长度
当 n x t nxt n x t 数组维护到第 i i i 位时,
如果 s n x t i + 1 = s i + 1 s_{nxt_i+1}=s_{i+1} s n x t i + 1 = s i + 1 就把 n x t nxt n x t 数组 + 1 +1 + 1 ,
否则让 n x t nxt n x t 数组 跳 n x t nxt n x t 。
匹配时直接暴力拓展,失配时跳 n x t nxt n x t 而不是从头开始。
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 int nxt[Maxn];void init (char * s) { int n = strlen (s + 1 ); for (int i = 2 , j = 0 ; i <= n; ++i) { while (j && s[j + 1 ] != s[i]) j = nxt[j]; if (s[j + 1 ] == s[i]) ++j; nxt[i] = j; } } vector< int > Ans; void check (char * s, char * s1) { int n = strlen (s + 1 ), m = strlen (s1 + 1 ); for (int i = 1 , j = 0 ; i <= m; ++i) { while (j && s[j + 1 ] != s1[i]) j = nxt[j]; if (s[j + 1 ] == s1[i]) ++j; if (j == n) { Ans.push_back (i - n + 1 ); j = nxt[j]; } } }
M a n a c h e r Manacher M ana c h er 回文匹配
时间复杂度:O ( n ) O(n) O ( n )
M a n a c h e r Manacher M ana c h er 是通过维护 R R R 和 m i d mid mi d 在线性时间内求一个模式串最长回文子串的算法,但它有一个明显的缺点就是只能判断长度为奇数的回文串,所以要在读入时进行特殊字符补位操作。
证明:
由于 R 是单调递增的,所以时间复杂度也是线性的。
具体实现:
在读入时先将第一位设为 ‘@’ 防止数组越界,后将读入字符和补 ‘#’ 交替进行,方便判偶数长度。
每到一位都要维护两个信息以便下次拓展,分别是这一位及以前所有回文子串右端点最大值 R R R 和拓展 R R R 到当前值的 m i d mid mi d 。
更新第 i 位时先继承回文半径为 i i i 关于 m i d mid mi d 的 对称点 j j j 的答案和 i i i 到 R R R 的距离中的最小值,后暴力拓展,同时更新 R R R 和 m i d mid mi d 。
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int scan (char * s) { char c = ' ' , s[0 ] = '~' , s[1 ] = '#' , int cnt = 1 ; while (c < 'a' || c > 'z' ) c = getchar (); while (c >= 'a' && c <= 'z' ) s[++cnt] = c, s[++cnt] = '#' , c = getchar (); return cnt; } int manacher (char * s, int n) { int R = 0 , pos, ans = 0 ; for (int i = 1 ; i <= n; ++i) { if (i <= R) r[i] = min (r[(pos << 1 ) - i], R - i + 1 ); while (s[i - r[i]] == s[i + r[i]]) r[i]++; if (i + r[i] > R) R = i + r[i] - 1 , pos = i; if (r[i] > ans) ans = r[i]; } return ans; }
字符串 T r i e Trie T r i e
详见
本文作者:CloudySky
写作时间: 2021-08-12
最后更新时间: 2021-10-09
遵循协议: BY-NC-SA