使用场景
- 在现实生活中有这样一个问题,如何把一篇文章中禁用的句子或单词(例如某些脏话)删去。如果把库(我们把禁用的句子或单词造成一个库)中的 每个串都和待配串(文章)进行判断子串的操作的话,效率也太低了。
基本流程
<1> build_trie
void build_tri(int x){//trie树:单词查找树
scanf("%s",str);
int cur=0;
for(int i=0;str[i];i++){
int t=str[i]-'a';
if(!ch[cur][t])ch[cur][t]=++num;
cur=ch[cur][t];
}to[x]=cur;//表示第i个匹配串的终止点也是fail树上的点 ,于是我们通过to[i] 就可以"接入"fail树了
}
<2> build_AC
void build_AC(){//我们利用Bfs来造树
int l=0,r=0;
for(int i=0;i<26;i++)if(ch[0][i])Q[r++]=ch[0][i];
while(l<r){
int x=Q[l++];
for(int i=0;i<26;i++){
if(ch[x][i]) Q[r++]=ch[x][i],fail[ch[x][i]]=ch[fail[x]][i];
else ch[x][i]=ch[fail[x]][i];//用fail填空儿子
}
}
}
<3> build_fail
for(int i=0;i<=num;i++)Add(fail[i],i);//建fail树由fail[i]指向i
<4> pre_operation
<5> run_word & update
void run_word(){
scanf("%s",str);
for(int i=0,cur=0;str[i];i++)
cur=ch[cur][str[i]-'a'];
//可以在过程中更新收集节点的信息
}
例题
- 我们先给出一个匹配串库(包含n个串)
- 有m个操作:
- 添加一个待配串
- 询问第t个匹配串被多少个待配串包含
- 暴力
- 每次更新都把串一个一个扫过来,输出ans[to[t]]即可
void update(){
int cur=0,r=0;
for(int i=0;str[i];i++){
int k=cur=ch[cur][str[i]-'a'];
while(k&&!mark[k]){
Q[r++]=k;
mark[k]=1;
k=fail[k];
}
}for(int i=0;i<r;i++)mark[Q[i]]=0,++ans[Q[i]];
}
- 思考:
- 对于每一个待配串,我们在读入它的时候就把它在tire树上跑一遍,每当一个匹配串被匹配成功时就把它的val加一。
- 可惜如果出现了一个待配串中包含多个相同的匹配串时就不行了
- 此时我们可以运用类似七彩树的方法来解这题。
- 分析
- 建fail树,(fail树的父节点权等于其子树权的和加原本的权)
- 每次跑一下待配串,匹配成功后会个每个匹配成功的点的权加一,而子节点被增加时其父节点都增加了,应当减去
- 于是我们可以把所有要增加的点储存下来,按dfn排序让相邻两者的lca减1,自身加1,这显然可以利用树状数组维护
/*
由于一些匹配串可以在带配串上出现多次,例如有一个待配串abdabdc和一个匹配串abd我们发现
abd在fail树上就 被加了两次了,于是我们在LCA上减掉他们
我们按照dfn来排序得到的节点一定保证每个相邻节点的LCA深度最深
*/
bool cmp(int x,int y){return L[x]<L[y];}
void run_word(){
scanf("%s",str);
int k=0;
for(int i=0,cur=0;str[i];i++){
cur=ch[cur][str[i]-'a'];
s[k++]=cur;
}
sort(s,s+k,cmp);
for(int i=0;i<k;i++){
add(L[s[i]],1);
if(i)add(L[LCA(s[i],s[i-1])],-1);
}
}
void solve(){
while(m--){
scanf("%d",&t);
if(t==1)run_word();
else{
scanf("%d",&t);
t=to[t];//我们找到要查询的串
printf("%d\n",sum(R[t])-sum(L[t]-1));
}
}
}