字符串
0 - 定义
string s 或 char s[]。
1 - 字典树(\texttt{Trie} )
将
n 个字符串存储在一棵树中。每个字符代表一条边,根节点到每个单词节点经过的所有边代表了所有的字符串。
实现:
const int maxn=2e5+3;
const int maxa=26 +3;
string s;
bool word[maxn];
int ch[maxn][maxa];
int init(int n){ // 建树
int tot=0;
for(int i=1;i<=n;i++){
cin>>s;
int len=s.length();
int now=1;
for(int j=0;j<len;j++){
int &to=ch[now][s[j]-'a'];
if(!to)to=++tot;
now=to;
}
}
return tot;
}
void query(int q){ // 查询
for(int i=1;i<=q;i++){
cin>>s;
int len=s.length();
int now=0;
for(int j=0;j<len;j++){
int &to=ch[now][wd[s[j]]];
if(!to){ cout<<"0\n"; goto ed; }
now=to;
}
ed:;
}
}
1.1 - 01 Trie
特殊的 Trie,仅由
而二进制数可以看作
2 - 字符串匹配问题
2.1 - 单模式串匹配问题
给定模式串
s 和文本串t ,查询文本串t 是否在s 中出现,以及所有的出现位置。
暴力匹配
在暴力匹配的过程中,如果发现
如果匹配发现
这个合适的位置只与
计算失配函数的时候就是
2.1.1 - KMP(Knuth-Morris-Pratt)
实现:
const int maxn=2e5+3;
const int maxa=26 +3;
char s[maxn],t[maxn];
int lens,lent;
int fail[maxn];
void init(){
cin>>s+1; cin>>t+1;
lens=strlen(s+1); lent=strlen(t+1);
}
void getfail(){
fail[0]=-1;
for(int i=0;i<lent;i++){
int j=fail[i];
while(~j&&t[j+1]!=t[i+1]) j=fail[j];
fail[i+1]=j+1;
}
}
void solve(){
for(int i=1,j=0;i<=lens;i++){
while(~j&&s[i ]!=t[j+1]) j=fail[j];
j++;
if(j==lent) cout<<i-lent+1<<'\n', j=fail[j];
}
}
fail 的性质
哈希可以枚举位置然后
2.1.2 - Hash
将字符串看作
其中
自然溢出:
如果两个串哈希值相等,就认为这两个串是相同的。
由于可以将字符串转换成数字,哈希值的应用相当广泛。
哈希冲突
如果有两个不同的串哈希值相同,就说这两个串哈希值冲突了。
如果要计算
所以当
区间哈希值
对于一个串
预处理前缀哈希
也就是说通过预处理可以
是文本串个数为
2.2 - 多模式串匹配问题
给定模式串
s 和n 个文本串\{t_i \} ,查询每个文本串t_i 是否在s 中出现的次数。
使用 KMP 的话,每个文本串都要
将所有文本串建 Trie 树,那么暴力匹配的话就是在 Trie 树上走,如果一个节点失配了,我们可以类似 KMP 定义一个失配指针,指向某个节点,这里的失配指针就可以从一个文本串跳到另一个文本串。
先建出 Trie 树,然后求失配指针。求失配指针的过程与 KMP 是类似的,注意由于失配指针指向的点未必是祖先,但是一定深度变小,因此我们需要 bfs Trie 树上的节点来求失配指针。
但是为了保证复杂度以及为了方便,我们可以记
这个 DAG ,称为字典图,求出字典图,那么匹配的时候就不需要跳