int ch[1000005][26],cnt[1000005],idx; string pat; voidbuild_Trie(){ int pos=0;//当前位置信息 for(int i = 0;pat[i];i++){ int j = pat[i]-'a'; if(!ch[pos][j])ch[pos][j]=++idx; pos=ch[pos][j]; } ++cnt[pos];//当前节点代表多少字符串 }
build_AC
在Trie树的基础上增加回跳边和转移边
树边:Trie中构建的边 (灰色边)
回跳边:指向当前节点的最长后缀。构建时,回跳边指向父节点的回跳边所指节点的儿子 (黄色边)
转移边:指向当前节点回跳边所指节点的儿子。(黑色边)
1 2 3 4 5 6 7 8 9 10 11 12 13
int ch[1000005][26],nex[1000005]/* 存储回跳边(等同于KMP中的nex[],名字都一样QaQ) */; voidbuild_AC(){ queue<int>q;//BFS建立自动机 for(int i = 0;i<26;i++) if(ch[0][i])q.push(ch[0][i]);//0号节点虚点,特殊处理,把所有的模式串开头加入队列 while(q.size()){ int now = q.front();q.pop(); for(int i = 0 ;i<26;i++){ int to = ch[now][i]; if(to)nex[to]=ch[nex[now]][i],q.push(to);//如果有当前儿子存在,就用当前节点回跳边指向节点的儿子更新当前儿子的回跳边 else ch[now][i]=ch[nex[now]][i];//如果这个方向没有孩子,就用当前节点回跳边的孩子更新当前节点在这个方向的转移边 } } }
query
查询有多少模式串与主串匹配
扫描主串,依次取出每个字符,并建立两个指针$i,j$
$i$指针走主串对应的节点,沿着树边或转移边走,保证不回退;
$j$指针沿着回跳边走,每次从当前节点回到根节点($0$),把以当前节点为止的后缀串全部取出不遗漏
1 2 3 4 5 6 7 8 9 10
intquery(){ int ans=0; for(int k = 0,i=0;s[k];k++){ i=ch[i][s[k]-'a'];//扫描主串 for(int j = i;j&&~cnt[j]/* ① */;j=nex[j]/* j指针走回跳边,跳回根节点 */){ ans+=cnt[j],cnt[j]=-1/* 同①处,每次将当前节点代表字符串取出后,清零当前节点计数 */; } } return ans;//返回统计答案 }