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 }

 

你可能感兴趣的:(AC自动机)