題目連結:http://acm.hdu.edu.cn/showproblem.php?pid=1277
推薦一篇部落格(看思路就可以,實現用的是java):
https://www.cnblogs.com/nullzx/p/7499397.html
這是一道模板題,拿來練手,之前看了一篇部落格,有點錯誤,但是hdu上面居然過了,最主要的是我在hdu上面三道AC自動機模板題都是這個錯的程式碼,居然都過了,害的我糾結了一晚上,原來是資料太水了。
主要還是看上面的部落格,寫了點註釋,不一定對,以後好拿來複習。
程式碼(指標版):
#include<iostream> #include<cstring> #include<algorithm> #include<queue> #include<map> #include<stack> #include<cmath> #include<vector> #include<set> #include<cstdio> #include<string> #include<deque> using namespace std; typedef long long LL; #define eps 1e-8 #define INF 0x3f3f3f3f #define maxn 10005 char str[maxn][62]; char s[62]; struct node{ int id;//記錄當前關鍵字的編號 node *next[10];//指向兒子 node *fail;//失配後指向的地方,相當於KMP裡面的next陣列的作用 node(){ id=-1; memset(next,0,sizeof(next)); fail=NULL; } }; queue<int>qe;//記錄出現的關鍵字編號 queue<node*>q;//BFS int n,m,k,t; void insert(char *s,node *root,int ID){//插入關鍵字到trie裡面 node *p=root; int len=strlen(s); for(int i=0;i<len;i++){ int id=s[i]-'0'; if(p->next[id]==NULL) p->next[id]=new node(); p=p->next[id]; } p->id=ID;//在關鍵字結束的地方把id更新為關鍵字的ID } void build_fail(node *root){//構建fail指標,要和KMP演算法聯想起來作比較 while(!q.empty()) q.pop(); q.push(root); while(!q.empty()){ node *temp=q.front(); q.pop(); for(int i=0;i<10;i++){ if(temp->next[i]==NULL) continue; if(temp==root)//讓root節點的所有兒子的fail指標都指向root,相當於next[0]=-1 temp->next[i]->fail=root; else{ node *p=temp->fail;//把temp->fail賦給p,因為接下來p要改變 while(p!=NULL){//一直向fail方向走,直到找到某個點的next[i]!=NULL或者p==NULL(找不到) if(p->next[i]!=NULL){ temp->next[i]->fail=p->next[i]; break; } p=p->fail; } if(p==NULL)//如果找不到,就讓temp的兒子i的fail指標指向根 temp->next[i]->fail=root; } q.push(temp->next[i]); } } } void query(node *root){ while(!qe.empty()) qe.pop(); node *temp=root; for(int i=0;i<n;i++){ for(int j=0;j<60;j++){ int id=str[i][j]-'0'; //一直往fail指向的方向找,直到找到或者到了root,相當於KMP裡面K一直等於next[K],最後匹配成功或者K=-1 while(temp->next[id]==NULL&&temp!=root) temp=temp->fail; temp=temp->next[id];//temp下移 if(temp==NULL) temp=root; node *p=temp; while(p!=root){//在所有的以i+'0'字元結尾的字首裡面找以這個字元結束的單詞 if(p->id!=-1){//注意這個p->id的位置,剛剛學,看了錯的部落格,把它寫在了上面,hdu居然過了,糾結了我老半天 qe.push(p->id); p->id=-1;//因為可能會有多個字尾和某一個字首相同,可能會重複計數,所以我們要標記一下 } p=p->fail; } } } } void destroy(node *root){ if(root==NULL) return; for(int i=0;i<10;i++){ if(root->next[i]!=NULL) destroy(root->next[i]); } delete root; } int main() { while(scanf("%d%d",&n,&m)!=EOF){ node *root=new node(); for(int i=0;i<n;i++) scanf("%s",str[i]); getchar(); char c; for(int i=0;i<m;i++){ int num=0;//計算空格數量,一旦達到了三個就說明接下來開始輸入關鍵字了 while(num!=3&&(c=getchar())){ if(c==' ') num++; } scanf("%s",s);//輸入關鍵字 insert(s,root,i);//把關鍵字插入字典樹 } build_fail(root);//構建fail指標 query(root);//掃描 if(qe.size()==0) printf("No key can be found !\n"); else{ printf("Found key:"); while(!qe.empty()){ printf(" [Key No. %d]",qe.front()+1); qe.pop(); } printf("\n"); } destroy(root);//釋放記憶體 } return 0; }
程式碼(陣列版):
#include<iostream> #include<cstring> #include<algorithm> #include<queue> #include<map> #include<stack> #include<cmath> #include<vector> #include<set> #include<cstdio> #include<string> #include<deque> using namespace std; typedef long long LL; #define eps 1e-8 #define INF 0x3f3f3f3f #define maxn 600005 int trie[maxn][10],fail[maxn],val[maxn],ID[maxn]; char str[1005][65],s[65]; int n,m,t,cnt; queue<int>q; void init(){ memset(trie,0,sizeof(trie)); memset(fail,0,sizeof(fail));//讓所有點的fail都指向0(根節點) memset(val,0,sizeof(val)); cnt=0; } void insert(char *s,int k){ int root=0; for(int i=0;s[i];i++){ int id=s[i]-'0'; if(trie[root][id]==0) trie[root][id]=++cnt; root=trie[root][id]; } val[root]++;//標記單詞結尾 ID[root]=k;//記錄ID } void build_fail(){//構建fail指標 while(!q.empty()) q.pop(); int root=0; for(int i=0;i<10;i++){//把root的兒子都壓入佇列 if(trie[root][i]) q.push(trie[root][i]); } while(!q.empty()){ int u=q.front(); q.pop(); for(int i=0;i<10;i++){ if(trie[u][i]){//如果u的兒子i存在,然它兒子的fail指向fail[u]的兒子,並壓入佇列 fail[trie[u][i]]=trie[fail[u]][i]; q.push(trie[u][i]); }else{//如果不存在,把fail[u]的兒子i變成u的兒子 trie[u][i]=trie[fail[u]][i]; } } } } void query(){ while(!q.empty()) q.pop(); int u=0; for(int i=0;i<n;i++){ for(int j=0;j<60;j++){ int id=str[i][j]-'0'; u=trie[u][id];//u在trie樹上往下移 int temp=u;//臨時儲存u while(temp!=0){//遍歷temp和它的fail指向的所有以str[i][j]結尾的字首,看裡面有多少個是單詞結尾,這裡可以加一個&&val[temp]!=0優化一下 if(val[temp]){//如果是單詞結尾 q.push(ID[temp]);//壓進佇列 val[temp]=0;//把標記去掉,防止重複計算 } temp=fail[temp];//往fail方向走,直到回到根節點 } } } } int main() { while(scanf("%d%d",&n,&m)!=EOF){ init(); for(int i=0;i<n;i++){ scanf("%s",str[i]); } getchar(); for(int i=0;i<m;i++){ int num=0; char c; while(num!=3&&(c=getchar())){ if(c==' '){ num++; continue; } } scanf("%s",s); insert(s,i); } build_fail(); query(); if(q.size()==0) printf("No key can be found !\n"); else{ printf("Found key:"); while(!q.empty()){ printf(" [Key No. %d]",q.front()+1); q.pop(); } printf("\n"); } } return 0; }