字符串学习笔记

字符串学习笔记(2019-12-03)

文章目录

  • 字符串学习笔记(2019-12-03)
    • Hash
      • 方法
      • 扩展
      • 代码实现
    • KMP
      • 前缀函数
      • 算法流程
      • 代码实现
      • 应用
        • 查询文本中的某个单词
        • 统计每个前缀的出现次数(未学习)
    • 扩展KMP(未学习)
    • Trie(未学习)
    • AC自动机
      • 失配指针
      • 算法流程(待完善)
      • 代码实现
    • 后缀数组(未学习)
    • 后缀树(未学习)
    • 后缀自动机(未学习)
    • Manacher
      • 算法流程
      • 代码实现
    • 回文自动机
    • 最小最大表示法
      • 循环同构
      • 最小最大表示
      • 算法流程
      • 代码实现
    • C++库函数
      • String
        • substr
        • find
      • Char
        • strcmp
        • strcpy
    • 参考资料

Hash

将某一个字符串映射到某一个值,使得其方便操作与查找,同时又要尽可能降低冲突率

方法

采用多项式Hash法: f ( s ) = ∑ s [ i ] × b i   ( m o d   M ) \displaystyle f(s)=\sum s[i]\times b^i\ (mod\ M) f(s)=s[i]×bi (mod M),使 b b b M M M互质,可以使错误率降低到 1 M \displaystyle \frac{1}{M} M1

扩展

  • 单/双/多模数Hash(模数大小)
  • 自然溢出Hash
  • 区间Hash
  • 具有单调性的Hash函数(待深入研究)

代码实现

typedef long long ll; // 可以改为自然溢出
const int M = 998244353;
const int B = 131; // 可以改为双模数
const int N = 100010;

int n;
ll f[N], b[N]; // 可以改用map来存储(时间复杂度多一个log)
char s[N];

inline void init()
{
	b[0] = 1;
	for (register int i = 1; i <= n; ++i) {
		f[i] = (f[i - 1] * B + s[i]) % M;
		b[i] = (b[i - 1] * B) % M;
	}
}

inline ll get_hash(int l, int r)
{
	return (f[r] - f[l - 1] * b[r - l + 1] % M + M) % M;
}

KMP

单字符串匹配。

前缀函数

对于第 i i i个位置,定义前缀函数 π [ i ] \pi[i] π[i],满足 π [ i ] = max ⁡ k = 1 , 2 , 3 , . . . , i { k ∣ s [ 1 , 2 , . . . , k ] = s [ i − k + 1 , i − k + 2 , . . . , i ] } \displaystyle \pi[i]=\max\limits_{k=1,2,3,...,i}\{k|s[1,2,...,k]=s[i-k+1,i-k+2,...,i]\} π[i]=k=1,2,3,...,imax{ks[1,2,...,k]=s[ik+1,ik+2,...,i]}
较为显然的是,相邻两个位置的前缀函数值最多变化1

算法流程

  • π [ 1 ] = 0 \pi[1]=0 π[1]=0 j = π [ i − 1 ] j=\pi[i-1] j=π[i1]
  • i ≥ 2 i \geq 2 i2,若 s [ i ] = s [ j + 1 ] s[i]=s[j+1] s[i]=s[j+1],则 π [ i ] = j + 1 \pi[i]=j+1 π[i]=j+1
  • 否则,令 j = π [ j ] j=\pi[j] j=π[j],重复步骤2,直到 j = 0 j=0 j=0
  • j = 0 j=0 j=0,令 π [ i ] = 0 \pi[i]=0 π[i]=0
  • 考虑之后的每一个位置完成整个字符串

代码实现

const int N = 100010;
int n, p[N];
char s[N];

inline void kmp()
{
	p[1] = 0;
	for (register int i = 2; i <= n; ++i) {
		register int j = p[i - 1];
		while (j > 0 && s[i] != s[j + 1]) j = p[j];
		if (s[i] == s[j + 1]) ++j;
		p[i] = j;
	}
}

应用

查询文本中的某个单词

设单词串为 s s s,文本串为 t t t,构造新串 w = s + # + t w=s+\#+t w=s+#+t,其中 # \# #为不在 s s s t t t中出现的字符。
对串 w w w使用KMP算法,若某位置满足 π [ i ] = s . l e n g t h \pi[i]=s.length π[i]=s.length,表示字符串 w [ i − s . l e n g t h + 1 , i − s . l e n g t h + 2 , . . . , i ] w[i-s.length+1,i-s.length+2,...,i] w[is.length+1,is.length+2,...,i]与字符串 s s s相同,即在文本中找到该单词。
时间复杂度: O ( n ) O(n) O(n)

统计每个前缀的出现次数(未学习)

扩展KMP(未学习)

Trie(未学习)

AC自动机

AC自动机以Trie树为结构基础,以kmp为思想建立的。

失配指针

状态 u u u的失配指针只想 v v v,且 v ∈ Q v \in Q vQ v v v u u u的最长后缀。

算法流程(待完善)

代码实现

#include 
using namespace std;

const int N = 1000010;
const int S = 55;

char s[S], a[N];

int t[N][26], tot;
int e[N], fail[N];

inline void insert(char *s)
{
	register int now = 0;
	for (register int i = 1; s[i]; ++i) {
		if (!t[now][s[i] - 'a']) t[now][s[i] - 'a'] = ++tot;
		now = t[now][s[i] - 'a'];
	}
	++e[now];
}

queue<int> q;
inline void build()
{
	for (register int i = 0; i < 26; ++i) {
		if (t[0][i]) q.push(t[0][i]);
	}
	while (q.size()) {
		register int now = q.front(); q.pop();
		for (register int i = 0; i < 26; ++i) {
			if (t[now][i]) {
				fail[t[now][i]] = t[fail[now]][i], q.push(t[now][i]);
			} else {
				t[now][i] = t[fail[now]][i];
			}
		}
	}
}

inline int query(char *s)
{
	register int now = 0, ret = 0;
	for (register int i = 1; s[i]; ++i) {
		now = t[now][s[i] - 'a'];
		for (register int j = now; j && e[j]; j = fail[j]) {
			ret += e[j], e[j] = 0;
		}
	}
	return ret;
}

inline void del()
{
	fail[0] = e[0] = 0;
	for (register int i = 0; i < 26; ++i) {
		if (t[0][i]) q.push(t[0][i]);
		t[0][i] = 0;
	}
	while (q.size()) {
		register int now = q.front(); q.pop();
		fail[now] = e[now] = 0;
		for (register int i = 0; i < 26; ++i) {
			if (t[now][i]) q.push(t[now][i]);
			t[now][i] = 0;
		}
	}
}

int main()
{
	register int T;
	scanf("%d", &T);
	while (T--) {
		register int n;
		scanf("%d", &n);
		for (register int i = 1; i <= n; ++i) {
			scanf("%s", s + 1);
			insert(s);
		}
		build();
		scanf("%s", a + 1);
		printf("%d\n", query(a));
		del();
	}
	return 0;
}

后缀数组(未学习)

后缀树(未学习)

后缀自动机(未学习)

Manacher

单回文串匹配。

算法流程

  • 构造一个形如 s = # a # b # a # b # a # b # c # s=\#a\#b\#a\#b\#a\#b\#c\# s=#a#b#a#b#a#b#c#的长度为 2 n + 1 2n+1 2n+1的字符串,同时维护已找到的子回文串最靠右的边界 ( l , r ) (l,r) (l,r),初始置 l = 0 , r = − 1 l=0,r=-1 l=0,r=1
  • 对于第 i i i个位置:
    • 如果 i > r i>r i>r:运用朴素办法进行检查,更新 d [ i ] d[i] d[i] ( l , r ) (l,r) (l,r)
    • 如果 i ≤ r i \leq r ir:(判断内回文串是否达到外回文串的边界)
      • 如果 i + d [ l + r − i ] − 1 ≥ r i+d[l + r - i]-1 \geq r i+d[l+ri]1r:令 d [ i ] = r − i d[i]=r-i d[i]=ri
      • 否则,令 d [ i ] = d [ l + r − 1 ] d[i] = d[l + r - 1] d[i]=d[l+r1]
      • 然后运用朴素办法进行检查,更新 d [ i ] d[i] d[i] ( l , r ) (l,r) (l,r)

代码实现

register int len = strlen(s + 1), n = 0, ans = 0;
t[++n] = '#';
for (register int i = 1; i <= len; ++i) {
	t[++n] = s[i];
	t[++n] = '#';
}
t[++n] = '\0';
for (register int i = 1, l = 1, r = 0; i <= n; ++i) {
	register int k = (i > r) ? 1 : min(d[l + r - i], r - i);
	while (1 <= i - k && i + k <= n && t[i - k] == t[i + k]) ++k;
	d[i] = k--;
	if (i + k > r) l = i - k, r = i + k;
	ans = max(ans, d[i] - 1);
}

回文自动机

最小最大表示法

循环同构

如果存在字符串 S [ i , i + 1 , i + 2 , . . . , n ] + S [ 1 , 2 , 3 , . . . , i − 1 ] = T S[i, i+1,i+2,...,n]+S[1,2,3,...,i-1]=T S[i,i+1,i+2,...,n]+S[1,2,3,...,i1]=T,则称字符串 S S S与字符串 T T T同构。

最小最大表示

字符串 S S S的最小表示为所有字符串 S S S的循环同构中字典序最小的字符串。
字符串 S S S的最大表示为所有字符串 S S S的循环同构中字典序最大的字符串。

算法流程

  • 初始化两个指针 i = 1 i=1 i=1 j = 2 j=2 j=2,匹配长度 k = 0 k=0 k=0
  • 比较 s [ i + k ] s[i+k] s[i+k] s [ j + k ] s[j+k] s[j+k]的大小:
    • s [ i + k ] > s [ j + k ] s[i+k]>s[j+k] s[i+k]>s[j+k] i = i + k − 1 i=i+k-1 i=i+k1
    • s [ i + k ] = s [ j + k ] s[i+k]=s[j+k] s[i+k]=s[j+k] k = k + 1 k=k+1 k=k+1
    • s [ i + k ] < s [ j + k ] s[i+k]s[i+k]<s[j+k] j = j + k − 1 j=j+k-1 j=j+k1
  • 操作过程中如果遇到 i = j i=j i=j,则随便令其中一项加一即可,答案最终为 m i n ( i , j ) min(i,j) min(i,j)
  • 最大表示法只需要将比较中的不等号改变方向即可。

代码实现

const int N = 100010;
char s[N];

inline int small()
{
    register int i = 1, j = 2, k = 0;
    while (i <= n && j <= n && k <= n) {
        if (s[(i + k) % n + 1] == s[(j + k) % n + 1]) ++k;
        else if (s[(i + k) % n + 1] > s[(j + k) % n + 1]) i = i + k + 1, k = 0;
        else j = j + k + 1, k = 0;
        if (i == j) ++i;
    }
    return min(i, j);
}

C++库函数

String

substr

s.substr(i, len)
提取 s [ i , i + 1 , i + 2 , . . . , i + l e n − 1 ] s[i,i+1,i+2,...,i+len-1] s[i,i+1,i+2,...,i+len1],即从第 i i i个位置获得长度为 l e n len len的子串。

find

s.find(t)
查询字符串 s s s中是否含有字符串 t t t,若不含有返回string::npos,否则返回其第一次出现的首字母的索引。

Char

strcmp

strcmp(s + 1, t + 1)
若前者字典序小于后者字典序,返回负值;若两者字典序相等,返回零;若前者字典序大于后者字典序,返回正值。

strcpy

strcpy(s + 1, t + 1)
将后者字符数组复制给前者。

参考资料

Oi-wiki

你可能感兴趣的:(ACM)