字符串
字符串哈希
KMP
KMP的精髓在于,对于每次失配之后,我们都不会从头重新开始枚举,而是根据我们已得知的数据,从某个特定的位置开始匹配;而对于模式串的每一位,都有唯一的“特定的变化位置”,这个在适配之后的特定变化位置可以帮助我们利用已有的数据不用从头匹配,从而节约数据。
next数组(KMP数组)
用
next数组记录模式串前缀的真前缀和真后缀最大相同位置。
int next[M];
void getnext(string&s)
{
next[1]=0;
for(int i=2,j=0;i<s.size();i++)
{
while(j>0&&s[j+1]!=s[i])j=next[j];
if(s[j+1]==s[i])j++;
next[i]=j;
}
}
/*
abcab
next: 1 2 3 4 5
0 0 0 1 2
*/
字符串匹配
string a,b;
cin>>a>>b;
a=' '+a;//文本串
b=' '+b;//模式串
getnext(b);
for(int i=1,j=0;i<=a.size();i++)
{
while(j>0&&b[j+1]!=a[i]) j=next[j];
if(b[j+1]==a[i]) j++;
if(j==b.size())
{
cout<<i-b.siez()+1<<endl;
j=next[j];//接着往下进行比较
}
}
/*
acabcabcca
abcab
next: 1 2 3 4 5
0 0 0 1 2
*/
例题:
[【模板】KMP 字符串匹配](P3375 【模板】KMP 字符串匹配 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)) [代码](记录详情 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
字典树

可以发现,这棵字典树用边来代表字母,而从根结点到树上某一结点的路径就代表了一个字符串。举个例子,\(1\to4\to 8\to 12\)表示的就是字符串
caa。trie 的结构非常好懂,我们用 \(\delta(u,c)\) 表示结点 \(u\)的\(c\) 字符指向的下一个结点,或着说是结点\(u\) 代表的字符串后面添加一个字符 \(c\) 形成的字符串的结点。(\(c\)的取值范围和字符集大小有关,不一定是 \(0\sim 26\)。)
有时需要标记插入进 tree 的是哪些字符串,每次插入完成时在这个字符串所代表的节点处打上标记即可。
映射字符
\(A\sim Z\to0\sim 26\)
\(a\sim z\to 26\sim 51\)
其余字符从映射到52之后
int getnum(char x)
{
if(x>='A'&&x<='Z')
return x-'A';
else if(x>='a'&&x<='z')
return x-'a'+26;
else
return x-'0'+52;
}
插入字符串
void insert(char str[])
{
int p=0,len=strlen(str);
for(int i=0;i<len;i++)
{
int c=getnum(str[i]);
//如果该边不存在过给他一个新的节点
if(!t[p][c])
t[p][c]=++cnt;
//如果存在继续插入
p=t[p][c];
cnt[p]++;
}
}
查找操作
int find(char str[])
{
int p=0,len=strlen(str);
for(int i=0;i<len;i++)
{
int c=getnum(str);
if(!t[p][c])
return 0;
p=t[p][c];
}
return cnt[p];
}
主函数
for(int i=1;i<=n;i++)
{
cin>>s;
insert(s);
}
for(int i=1;i<=q;i++)
{
cin>>s;
find(s);
}
例题

浙公网安备 33010602011771号