trie樹
trie樹就是字典樹,可以理解爲單詞樹,樹上每條邊是字母,被標記的節點表示根到這個節字母組成了單詞。
數據結構:用二維數組trie[maxn][N],tire[u][c]表示樹上編號爲u的父節點以邊爲c單詞連接到的兒子的編號。
創建trie樹:每次添加一個單詞,若當前路徑已建立此以連接此單詞爲邊的兒子節點就沿着走,否則建立新的節點。
學習鏈接:淺談Trie樹
Trie樹模板
const int maxn=5e5+7;
const int N=26;
struct Tire{
int trie[maxn][N],tot;
bool book[maxn];
void Init(){
memset(trie,0,sizeof trie);
memset(book,0,sizeof book);
tot=0;
}
void Insert(string a){
int u=0;
for(int i=0;i<a.size();++i){
int v=a[i]-'a';
if(trie[u][v]==0){
trie[u][v]=++tot;
}
u=trie[u][v];
}
book[u]=true;
}
}test;
例題:
Phone List
#include<iostream>
#include<set>
#include<string.h>
#include<string>
#include<stdio.h>
using namespace std;
struct Tire{
int trie[100100][11],tot;
bool book[100100];
bool Insert(string a){
int u=0;
bool f=false;
for(int i=0;i<a.size();++i){
int v=a[i]-'0';
if(book[u]==true) f=true;
if(trie[u][v]==0){
trie[u][v]=++tot;
}
u=trie[u][v];
}
if(book[u]==true) f=true;
book[u]=true;
for(int i=0;i<=9;++i){
if(trie[u][i]!=0) {
f=true;
break;
}
}
return f;
}
void Clear(){
memset(trie,0,sizeof trie);
memset(book,0,sizeof book);
tot=0;
}
}test;
string str;
int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
test.Clear();
scanf("%d",&n);
bool ans=false;
for(int i=1;i<=n;++i){
cin>>str;
if(test.Insert(str)){
ans=true;
}
}
if(ans) printf("NO\n");
else printf("YES\n");
}
return 0;
}
ac自動機
kmp是單模式串匹配,ac自動機可以跑多模式串匹配。
ac自動機是在trie樹上增加fail指針,用來實現當前節點失配時,跳轉到前綴與已匹配部分相同後綴相同的節點上。
Fail指針的實質含義就是:如果一個點i的Fail指針指向j。那麼root到j的字符串是root到i的字符串的一個後綴。
舉個栗子:
i:4 j:7
root到i的字符串是“ABC”
root到j的字符串是“BC”
“BC”是“ABC”的一個後綴
所以i的Fail指針指向j
查詢時:從頭開始遍歷文本串,對於文本串上的每個字母,不停地在樹上找前綴與當前已匹配部分的後綴相同的節點。
學習鏈接:AC自動機講解超詳細 ,AC自動機 算法詳解(圖解)及模板
ac自動機模板:
const int maxn=5e5+7;
const int N=26;
struct acAutomaton{
int trie[maxn][N],cntword[maxn],fail[maxn],tot;
void Clear(){
memset(trie,0,sizeof trie);
memset(cntword,0,sizeof cntword);
memset(fail,0,sizeof fail);
tot=0;
}
//創建字典樹
void insertWord(string str){
int u=0;
for(int i=0;i<str.length();++i){
int v=str[i]-'a';
if(trie[u][v]==0){
trie[u][v]=++tot;
}
u=trie[u][v];
}
++cntword[u];
}
void getFail(){
queue<int> q;
//將根節點存在的子節點扔進隊列
for(int i=0;i<26;++i){
if(trie[0][i]){
fail[trie[0][i]]=0;
q.push(trie[0][i]);
}
}
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=0;i<26;++i){
if(trie[u][i]==0){
//當前字母沒有爲i+'a'的子節點,將其子節點指向它fail指針的爲i+'a'子節點,(根節點的fail指針指向自己)
//因爲當前後綴和fail的前綴相同,可以共用子節點
trie[u][i]=trie[fail[u]][i];
}
else {
//爲的i+'a'子節點的fail指針指向父節點fail指針爲i+'a'的子節點
fail[trie[u][i]]=trie[fail[u]][i];
q.push(trie[u][i]);
}
}
}
}
int query(string str){
int u=0,ans=0;
for(int i=0;i<str.size();++i){
//對於一個字母,不停地在樹上找前綴與當前已匹配部分的後綴相同的節點,但是注意u指針不變
//當單詞被計算過或fail不存在時結束
u=trie[u][str[i]-'a'];
for(int j=u;j&&cntword[j]!=-1;j=fail[j]){
ans+=cntword[j];
cntword[j]=-1;
}
}
return ans;
}
}Test;
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+7;
struct acAutomaton{
int trie[maxn][26],cntword[maxn],fail[maxn],tot;
void Clear(){
memset(trie,0,sizeof trie);
memset(cntword,0,sizeof cntword);
memset(fail,0,sizeof fail);
tot=0;
}
//創建字典樹
void insertWord(string str){
int u=0;
for(int i=0;i<str.length();++i){
int v=str[i]-'a';
if(trie[u][v]==0){
trie[u][v]=++tot;
}
u=trie[u][v];
}
++cntword[u];
}
void getFail(){
queue<int> q;
//將根節點存在的子節點扔進隊列
for(int i=0;i<26;++i){
if(trie[0][i]){
fail[trie[0][i]]=0;
q.push(trie[0][i]);
}
}
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=0;i<26;++i){
if(trie[u][i]==0){
//當前字母沒有爲i+'a'的子節點,將其子節點指向它fail指針的爲i+'a'子節點
//因爲當前後綴和fail的前綴相同
trie[u][i]=trie[fail[u]][i];
}
else {
//爲的i+'a'子節點的fail指針指向父節點fail指針爲i+'a'的子節點
fail[trie[u][i]]=trie[fail[u]][i];
q.push(trie[u][i]);
}
}
}
}
int query(string str){
int u=0,ans=0;
for(int i=0;i<str.size();++i){
//對於一個字母,不停地在樹上找與其後綴相同的串
//當單詞被計算過或fail不存在結束
u=trie[u][str[i]-'a'];
for(int j=u;j&&cntword[j]!=-1;j=fail[j]){
ans+=cntword[j];
cntword[j]=-1;
}
}
return ans;
}
}Test;
string str;
int main(){
int n;
cin>>n;
Test.Clear();
for(int i=1;i<=n;++i){
cin>>str;
Test.insertWord(str);
}
Test.getFail();
cin>>str;
cout<<Test.query(str)<<endl;
return 0;
}