字符串入门
定义
前缀
对于字符串 $S$,从串首开始到某个位置 $i$ 结束的子串,叫做 $S$ 的一个前缀,除 $S$ 本身外的所有前缀叫做 $S$ 的真前缀。
后缀
与前缀类似,从某位置 $i$ 到串尾的一个字串叫做后缀,同样有真后缀。
字典树/Trie
结构为一棵树,从根节点到树上某一结点的一条路径就是一个字符串。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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43int id(char c){
if(c>='A'&&c<='Z')
return c-'A';
else if(c>='a'&&c<='z')
return c-'a'+26;
else
return c-'0'+52;
}
struct Trie{
int nxt[N][65],tot,cnt[N];
bool end[N];
void init(){
for(int i=0;i<=tot;i++)
for(int j=0;j<=64;j++)
nxt[i][j]=0;
for(int i=0;i<=tot;i++)
cnt[i]=0,end[i]=0;
tot=0;
return;
}
void insert(char s[]){
int p=0,len=strlen(s);
for(int i=0;i<len;i++){
int c=id(s[i]);
if(!nxt[p][c])
nxt[p][c]=++tot;
p=nxt[p][c];
cnt[p]++;
}
end[p]=1;
return;
}
int find(char s[]){
int p=0,len=strlen(s);
for(int i=0;i<len;i++){
int c=id(s[i]);
if(!nxt[p][c])
return 0;
p=nxt[p][c];
}
return cnt[p];
}
};
应用
字符串查找,前缀统计等。
一些关于后缀的操作可以反转字符串变为前缀操作。
KMP 算法
进行 KMP 算法的过程其实就是在求一个叫做最长公共前后缀的东西。
定义一个字符串 $S$ 的 border 为 $S$ 的一个非 $S$ 本身的子串 $T$,满足 $T$ 既是 $S$ 的前缀,又是 $S$ 的后缀。求解最大 border。1
2
3
4
5
6
7
8
9int j=0;
nxt[1]=0;//nxt[i]表示字串S[1...i]的最大border的长度
for(int i=2;i<=len;i++){
while(j&&s[j+1]!=s[i])
j=nxt[j];
if(s[j+1]==s[i])
j++;
nxt[i]=j;
}
应用
查找 $S_2$ 串在 $S_1$ 串的位置。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19int j=0;
for(int i=2;i<=len2;i++){
while(j&&s2[j+1]!=s2[i])
j=nxt[j];
if(s2[j+1]==s2[i])
j++;
nxt[i]=j;
}
j=0;
for(int i=1;i<=len1;i++){
while(j&&s2[j+1]!=s1[i])
j=nxt[j];
if(s2[j+1]==s1[i])
j++;
if(j==len2){
printf("%d\n",i-j+1);
j=nxt[j];
}
}
另一种方法是,构建一个新字符串 $S=S_2+a+S_1$,进行一次 KMP,若某点 $nxt$ 值等于 $S_2$ 的长度则找到了一个解。$a$ 为字符集以外的任意字符,如 #
。