统计前缀
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1000005;//树里面总的结点数,最多100000个单词但是结点数肯定多于100000
int n,m;
int tree[N][27] = {0};//tree[i][j]存的是结点i的第j个儿子的节点号,用这样的方式连起来
int fa[N] = {0};//找父亲
bool f[N] = {0};//判断是否为一个完整单词的标记
int tot = 0;//记总的结点数
char ch[15] = {0};
int sizze[N] = {0};//从树根到i结点为前缀的单词总个数,size不能用,会CE的
void insert(char *str){
int len = strlen(str);
int rt = 0;
for(int i=0;i<len;i++){
int id = str[i]-'a';
if(!tree[rt][id]) {
tree[rt][id] = ++tot;
fa[tot] = rt;
}
rt = tree[rt][id];
}
f[rt] = true;
while(rt){
sizze[rt]++;
rt = fa[rt];//倒着加一遍……复杂度可能不太妙
}
}
void find(char *str){
int len = strlen(str);
int rt = 0,ans = 0;
for(int i=0;i<len;i++){
int id = str[i]-'a';
if(!tree[rt][id]){
cout<<"0"<<endl;
return ;
}
rt = tree[rt][id];
}
cout<<sizze[rt]<<endl;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>ch;
insert(ch);
}
cin>>m;
for(int i=1;i<=m;i++){
cin>>ch;
find(ch);
}
return 0;
}
插入删除查找
#include<iostream>
#include<cstring>
using namespace std;
const int N = 3000005;
int n;
int fa[N] = {0},sizze[N] = {0};
int tree[N][27] = {0};
bool word[N] = {0};
int tot = 0;
char ch1[10] = {0},ch2[31] = {0};
void _insert(char *str){
int len = strlen(str);
int rt = 0;
for(int i=0;i<len;i++){
int id = str[i]-'a';
if(!tree[rt][id]){
tree[rt][id] = ++tot;
fa[tot] = rt;
}
rt = tree[rt][id];
}
word[rt] = true;
while(rt){
sizze[rt]++;
rt = fa[rt];
}
}
void _delete(char *str){
int len = strlen(str);
int rt = 0,idx;
for(int i=0;i<len;i++){
idx = str[i]-'a';
if(!tree[rt][idx]) return;//呃呃呃呃没意识到这里可能根本找不到这样的前缀,以为数据都合法呢
rt = tree[rt][idx];
}
while(rt){//注意呀……不一定是只删掉最后的那个结点,有的时候前面的也要删,因为说是删单词呀,是一整个一整个删掉的,只是看看具体在树上要删多少而已
if(sizze[rt]!=sizze[fa[rt]]) {
sizze[fa[rt]] -= sizze[rt];
tree[fa[rt]][str[len-1]-'a'] = 0;
break;
}
len--;
rt = fa[rt];
}
}
void _search(char *str){
int len = strlen(str);
int rt = 0;
for(int i=0;i<len;i++){
int id = str[i]-'a';
if(!tree[rt][id]){
cout<<"No"<<endl;
return ;
}
rt = tree[rt][id];
}
cout<<"Yes"<<endl;
return ;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>ch1>>ch2;
if(ch1[0]=='i') _insert(ch2);
if(ch1[0]=='d') _delete(ch2);
if(ch1[0]=='s') _search(ch2);
}
return 0;
}
树上KMP
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1000005;
int tree[2*N][26] = {0};//开2e6+5?
int tot = 0,n;
bool flagg[N*2] = {0};
char t[N] = {0},s[N] = {0};
int fail[N] = {0};
void _insert(char *str){
int len = strlen(str);
int rt = 0;
for(int i=0;i<len;i++){
int id = str[i]-'a';
if(!tree[rt][id]) {
tree[rt][id] = ++tot;
}
rt = tree[rt][id];
}
flagg[rt] = true;
}
// BFS to build the fail pointer on trie tree
// 如果已经求得了深度不高于结点t的各结点的fail指针,更新t的子节点的fail指针
// 需要额外注意回溯到根节点时的边界情况
void bfs(int u){
int prev;
for(int i=0;i<26;i++){
if(!tree[u][i]) continue;
int c = tree[u][i];
prev = fail[u];
while(tree[prev][i]==0 && prev != -1) prev = fail[prev];
if(prev != -1) fail[c] = tree[prev][i];
}
for(int i=0;i<26;i++){
if(tree[u][i]) bfs(tree[u][i]);
}
}
bool check(){
int i = 0,j = 0;
int len = strlen(t);
while(flagg[i]!=true && j<len){
if(i==-1){
i = 0;
}
if(tree[i][t[j]-'a']!=0){
i = tree[i][t[j]-'a'];
j++;
}
else if(i==0){
j++;
}
else{
i = fail[i];
}
}
if(flagg[i]) return true;
else return false;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>s;
_insert(s);
}
fail[0] = -1;
bfs(0);
cin>>t;
if(check())
cout<<"valar morghulis"<<endl;//yes
else cout<<"valar dohaeris"<<endl;//no
return 0;
}
注意哈,这里写kmp
还是跟一维匹配差不多的,一开始设置一个-1
,作为没有匹配上的标记,处理fail
数组的时候特别注意要关照好根节点。其他的和一维的类似,只是i++
这个语句变成树上的操作而已~