AC自动机

http://baike.baidu.com/view/8150013.htm

http://acm.hdu.edu.cn/showproblem.php?pid=2222

今天看到这道题 以为是KMP 刚学完 想拿来练手 谁知写完超时。去discuss看了看 说是用AC自动机过的 今天也没什么安排 就去了解了下 它是建立在KMP和trie树基础上的一种高效串匹配的算法

先将字符串建成一个字典树 标记每个字符串的尾部 建完之后 输入待匹配的字符串 这是只对这个字符串进行循环查找即可 判断每个字符是否是在字典树里出现 当循环到一个字符串的尾部时 num就会加上这个字符串的数量。

看着别人的模板打了一晚上 有些地方还是模模糊糊的 不过还是学了点东西

View Code
  1 #include<stdio.h>
  2 #include <iostream>
  3 #include<string.h>
  4 using namespace std;
  5 struct node
  6 {
  7     int count;//标记字符串的结束 和数量
  8     struct node *next[28];//指向 26个字母的子节点 最多只有26个 一个节点有26个字母域
  9     struct node *fail;//失败指针 与KMP的next函数类似
 10     node()//初始化函数 将节点的26个指针域都清空
 11     {
 12         fail = NULL;
 13         count = 0;
 14         memset(next,NULL,sizeof(next));
 15     }
 16 }*q[1000001];
 17 int tail,head;//队列的头尾
 18 char str[1000001];    
 19 void creat(char *c,struct node *root)//建立字典树
 20 {
 21     int i = 0,num;
 22     struct node *p = root;
 23     while(c[i])
 24     {
 25         num = c[i]-97;
 26         if(p->next[num]==NULL)
 27         {
 28             p->next[num] = new node;//若为空 为其创立新节点
 29         }
 30         i++;
 31         p=p->next[num];
 32     }
 33     p->count++;//到了字符串尾部 就加一
 34     
 35 }
 36 /*
 37 在字典树上构造fail指针。构造失败指针的过程概括起来就一句话:
 38 设这个节点上的字母为C,沿着他父亲的失败指针走,直到走到一个节点,
 39 他的儿子中也有字母为C的节点。然后把当前节点的失败指针指向那个字母也为C的儿子。
 40 如果一直走到了根节点都没找到,那就把失败指针指向根节点。
 41 所以构造fail指针 需要用到BFS。 保证是按层遍历字典树。
 42 */
 43 void bfail(struct node *root)//利用队列BFS实现层遍历 来找fail指针
 44 {
 45     int i;
 46     head = 0;
 47     tail = 0;
 48     root->fail = NULL;
 49     q[tail++] = root;
 50     while(head!=tail)
 51     {
 52         struct node *temp=q[head++];
 53         struct node *p = NULL;
 54         for(i = 0; i < 26 ; i++)
 55         {
 56             if(temp->next[i]!=NULL)
 57             {
 58                 if(temp==root)
 59                     temp->next[i]->fail = root;//所有根节点的子节点都指向根节点
 60                 else
 61                 {
 62                     p = temp->fail;
 63                     while(p!=NULL)
 64                     {
 65                         if(p->next[i]!=NULL)//找到一个节点的子节点有i字母 
 66                         {
 67                             temp->next[i]->fail=p->next[i];//设为fail指针
 68                             break;
 69                         }
 70                         p=p->fail;//循环找 与kmp类似
 71                     }
 72                     if(p==NULL)
 73                         temp->next[i]->fail = root;//若为空 指向根节点
 74                 }
 75                 q[tail++] = temp->next[i];
 76             }
 77         }
 78     }
 79 }
 80 /*
 81 匹配过程分两种情况:
 82 (1)当前字符匹配,表示从当前节点沿着树边有一条路径可以到达目标字符,
 83 此时只需沿该路径走向下一个节点继续匹配即可,目标字符串指针移向下个字符继续匹配;
 84 (2)当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,
 85 匹配过程随着指针指向root结束。重复这2个过程中的任意一个,直到模式串走到结尾为止。
 86 */
 87 int ac(struct node *root)
 88 {
 89     int i=0,k=strlen(str),d,num = 0;
 90     struct node *p = root;
 91     for(i = 0 ;i < k ; i++)
 92     {
 93         d = str[i]-97;
 94         while(p->next[d]==NULL&&p!=root)//字典树里找不到i字符 退回根节点
 95             p=p->fail;
 96         p=p->next[d];
 97         if(p==NULL)
 98             p = root;
 99         struct node *temp = p;
100         while(temp!=root&&temp->count!=-1)
101         {
102             num+=temp->count;
103             temp->count = -1;//避免重复记录
104             temp = temp->fail;
105         }
106     }
107     return num;
108 }
109 int main()
110 {
111     int i,j,n,t;
112     char c[52];
113     scanf("%d",&t);
114     while(t--)
115     {
116         scanf("%d%*c",&n);
117         struct node *root = new node;
118         for(i = 1; i <= n ; i++)
119         {
120             gets(c);
121             creat(c,root);
122         }
123         gets(str);
124         bfail(root);
125         printf("%d\n",ac(root));
126     }
127     return 0;
128     
129 }

 

posted @ 2012-07-22 12:08  _雨  阅读(194)  评论(0编辑  收藏  举报