估计在OJ上刷过题的都会对AC自动机这个名词很感兴趣,同样,记得去年ACM暑期集训的时候,在最后讲到字符串部分,听说了这个算法的名字之后就对于它心向往之,AC正好是Accept的简称,字面意义上的理解是一个可以让题目自动AC的东西,那这是有多厉害!很多次和同学开玩笑,都会提起这个名词。不过其实毕竟只是个字符串处理的算法,真正学起来还是费了不少力。
百度一下就会看到一个模版题:
hdu 2222 Keywords Search
1 #include<stdio.h> 2 #include<string.h> 3 #include<stdlib.h> 4 struct node{ 5 node *fail; 6 node *next[27]; 7 int count; 8 node() 9 { 10 fail=NULL; 11 count=0; 12 memset(next,NULL,sizeof(next)); 13 } 14 }*q[500001]; 15 char keyword[50]; 16 char text[10000000]; 17 int head,tail; 18 //建树 19 void insert(char str[],node *root) 20 { 21 node *p=root; 22 int i=0,index=0; 23 while(str[i]) 24 { 25 index=str[i]-'a'; 26 if(p->next[index]==NULL) 27 { 28 p->next[index]=new node(); 29 } 30 p=p->next[index]; 31 i++; 32 } 33 p->count++; 34 } 35 /*设这个节点上的字母为C,沿着他父亲的失败指针走, 36 直到走到一个节点,他的儿子中也有字母为C的节点。 37 然后把当前节点的失败指针指向那个字母也为C的儿子。如果一直走到了root都没找到, 38 那就把失败指针指向root。具体操作起来只需要:先把root加入队列(root的失败指针指向自己或者NULL), 39 这以后我们每处理一个点,就把它的所有儿子加入队列,队列为空。*/ 40 void bulidAC(node *root) 41 { 42 int i; 43 root->fail=NULL; 44 q[head++]=root; 45 while(head!=tail) 46 { 47 node *temp=q[tail++];//元素出栈, 48 node *p=NULL; 49 for(i=0;i<26;i++) 50 { 51 if(temp->next[i]!=NULL)//处理出栈元素的所有的子元素的fail指针 52 { 53 if(temp==root)temp->next[i]->fail=root;//根元素的下一级置失败指针 54 else 55 { 56 p=temp->fail;//父元素的失败指针 57 while(p!=NULL) 58 { 59 if(p->next[i]!=NULL) 60 { 61 temp->next[i]->fail=p->next[i]; 62 break; 63 } 64 p=p->fail; 65 } 66 if(p==NULL)temp->next[i]->fail=root;//如果最终指向空,当前子元素指根元素 67 } 68 q[head++]=temp->next[i]; 69 } 70 } 71 } 72 } 73 /* 74 (1)当前字符匹配,表示从当前节点沿着树边有一条路径可以到达目标字符, 75 此时只需沿该路径走向下一个节点继续匹配即可,目标字符串指针移向下个字符继续匹配; 76 (2)当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配, 77 匹配过程随着指针指向root结束。重复这2个过程中的任意一个,直到模式串走到结尾为止。*/ 78 int match(node *root) 79 { 80 int i=0,cnt=0,index; 81 node *p=root; 82 while(text[i]) 83 { 84 index=text[i]-'a'; 85 while(p->next[index]==NULL&&p!=root)//当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配 86 p=p->fail; 87 p=p->next[index]; 88 p=(p==NULL)?root:p; 89 node *temp=p; 90 while(temp!=root&&temp->count!=-1) 91 { 92 cnt+=temp->count; 93 temp->count=-1; 94 temp=temp->fail; 95 } 96 i++; 97 } 98 99 return cnt; 100 } 101 int main() 102 { 103 int n,t; 104 scanf("%d",&t); 105 while(t--){ 106 head=tail=0; 107 node *root=new node(); 108 scanf("%d",&n); 109 getchar(); 110 while(n--){ 111 gets(keyword); 112 insert(keyword,root); 113 } 114 bulidAC(root); 115 scanf("%s",text); 116 printf("%d ",match(root)); 117 } 118 system("Pause"); 119 return 0; 120 }
这个题是从家到厦门之前A的最后一个题
题目分三个步骤:
1.建Trie树。通过将keyword全部贴到这个树上。
2.建失败指针。原则是沿着父节点的失败指针往上寻到某个节点的儿子和当前结点字母相同即将该节点的失败指针赋给该节点。
3.匹配。
1.若当前结点无法继续匹配,则沿着失败指针去找到一个可以匹配的结点
2.到某个可以匹配的结点,要一直沿着失败指针往上找到根节点未知
对于abcd,bc两个keyword,abcde为匹配串,如果没有第二步,则只会输出1,即到d匹配完结束,只有失败指针向上寻找才可以将cd找出。
代码基本上是对着模版拍的,也大致理解了,看代码真不容易。。于是今天本想趁热打铁,再来一道简单的题练练手,结果被坑了一个下午。
hdu 2896 病毒侵袭
1 #include<stdio.h> 2 #include<string.h> 3 #include<stdlib.h> 4 5 struct node{ 6 node *fail; 7 node *next[94]; 8 int count; 9 int number; 10 node() 11 { 12 fail=NULL; 13 count=0; 14 memset(next,NULL,sizeof(next)); 15 number=0; 16 } 17 }*queen[1000000]; 18 char code[203]; 19 char text[100004]; 20 int head,tail; 21 int num[40],bound=0; 22 void insert(node *root,char str[],int num) 23 { 24 node *p=root; 25 int i=0,index=0; 26 while(str[i]) 27 { 28 index=str[i]-' '; 29 if(p->next[index]==NULL) 30 { 31 p->next[index]=new node(); 32 } 33 p=p->next[index]; 34 i++; 35 } 36 p->count++; 37 p->number=num; 38 } 39 void buildAC(node *root) 40 { 41 head=tail=0; 42 queen[head++]=root; 43 root->fail=NULL; 44 while(head!=tail) 45 { 46 node *now=queen[tail++]; 47 for(int i=0;i<94;i++) 48 { 49 if(now->next[i]!=NULL) 50 { 51 52 if(now==root){now->next[i]->fail=root;} 53 else 54 { 55 node *temp=now->fail; 56 while(temp!=NULL) 57 { 58 if(temp->next[i]!=NULL) 59 {now->next[i]->fail=temp->next[i]; 60 break; 61 } 62 temp=temp->fail; 63 } 64 if(temp==NULL) 65 now->next[i]->fail=root; 66 } 67 queen[head++]=now->next[i]; 68 } 69 } 70 71 } 72 } 73 int match(char str[],node *mid) 74 { 75 76 int flag=0; 77 int i=0,index=0; 78 int cnt=0; 79 node *p=mid; 80 while(str[i]) 81 { 82 index=str[i]-' '; 83 while(p->next[index]==NULL&&p!=mid) 84 p=p->fail; 85 p=p->next[index]; 86 p=(p==NULL)?mid:p; 87 node *temp=p; 88 while(temp!=mid) 89 { 90 cnt+=temp->count; 91 if(temp->count>0) 92 { 93 num[bound++]=temp->number; 94 } 95 temp=temp->fail; 96 } 97 i++; 98 } 99 return cnt; 100 } 101 int compare(const void*a,const void*b){return *(int *)a-*(int *)b;} 102 int main() 103 { 104 int N,M; 105 int ans=0; 106 node *root=new node(); 107 scanf("%d",&N); 108 for(int i=0;i<N;i++) 109 { 110 scanf("%s",code); 111 insert(root,code,i+1); 112 } 113 114 buildAC(root); 115 116 scanf("%d",&M); 117 for(int i=0;i<M;i++) 118 { 119 int res; 120 memset(num,0,sizeof(num)); 121 bound=0; 122 node *every=root; 123 memset(text,0,sizeof(text)); 124 scanf("%s",text); 125 res=match(text,every); 126 if(res!=0) 127 { 128 printf("web %d:",i+1); 129 qsort(num,bound,sizeof(int),compare); 130 for(int j=0;j<bound;j++) 131 printf(" %d",num[j]); 132 printf(" "); 133 ans++; 134 } 135 } 136 printf("total: %d ",ans); 137 return 0; 138 }
这个题目基本也算模版题,但客观情况是与上一题相比变化在与多组需要匹配的文本与ASCII字符集的范围改变了。
这两个问题尤其是后面一个困扰了很久,按照26个字母,造成数组越界6次而不明所以。
而另外一个纠结的问题则是指针,结构体指针,函数指针传值这些东西之间的关系。后来将count变为-1这个判断去掉,避开了这个问题。
从家回学校,中间又隔了不少天,在火车上收到了中科院面试的短信,有点安心,又有点担心,昨天回学校在宿舍待了一天,没啥作为,反而是装了dota2,被虐了好多局。囧,今天下午在宿舍调了一下午AC自动机代码,对于6号的面试也不一定有什么帮助,而要准备的东西还有很多。继续加油吧。