Problem Address:http://poj.org/problem?id=1204
【前言】
一道算是比较简单的AC自动机的题。
开始的时候,出于对第一份AC自动机代码的怀疑以及对上一份小变种的担忧,所有调试了好久。
现在发现,第一次的那份代码可靠性是极强的。
时限是5s,本来以为要跑很久,结果一出来1s都不到。爽快!
但是关于AC自动机的好多应用还是有很大难度的,还得继续努力学习。
【思路】
先简单地构造出AC自动机(以w个单词为词典)。
然后写查询函数。
简单地说,就是对puzzle里的每一个字母开始向八个方向形成八个字符串,在自动机中查询这些字符串以寻求匹配。
查询函数可参考:http://blog.csdn.net/Human_CK/archive/2011/06/27/6569992.aspx(只是参考)
对于给定的字符串,从根节点开始匹配,如果无法继续则转向其失败指针所指节点。
在匹配的过程中,如果发现某个节点是某个单词的结尾,即从根节点到该节点可形成一个单词,则说明在给定的字符串中匹配到了这个单词,把这个单词的开始位置以及其方向记录下来。记录过的单词可以标记,以便之后不再找到。不过由于这道题是Special Judge,所以有没有做这一步都无所谓,做了只是增加一点小开销罢了。
注意找到之后不能跳出循环,而应该继续搜索下去,否则无法达到要求。
如果对puzzle里的每个字母都进行八个方向的搜索,那么会有很多搜索是重复的。
比如从第一个字母向右,和从第二个字母向右,这两种情况,第一种是包含了第二种的。
所以可以总结为几种情况进行搜索。
当然,代码量也因此而有所增加。
【代码】
#include <iostream> #include <cstring> using namespace std; char map[1005][1005]; char word[1005]; struct node { node *fail; node *next[26]; int len; int w; }tire[1000005]; int total; node *root; node *q[1000005]; int head, tail; int record[1005][2]; char sign[1005]; inline node* new_node() { node *p = &tire[total]; total++; p->fail = NULL; p->len = 0; p->w = 0; memset(p->next, 0, sizeof(p->next)); return p; } void insert(node *root, char *s, int index) { node *p = root; int i=0, t; while(s[i]!='/0') { t = s[i] - 'A'; if (p->next[t]==NULL) p->next[t] = new_node(); p = p->next[t]; i++; } p->w = index; p->len = strlen(s); } void build_ac_automation(node *root) { int i; node *temp; root->fail = NULL; head = 1; tail = 0; q[0] = root; while(head!=tail) { temp = q[tail]; tail++; for (i=0; i<26; i++) { if (temp->next[i]!=NULL) { if (temp==root) { temp->next[i]->fail = root; } else { node *p = temp->fail; while(p!=NULL) { if (p->next[i]!=NULL) { temp->next[i]->fail = p->next[i]; break; } p = p->fail; } if (p==NULL) temp->next[i]->fail = root; } q[head] = temp->next[i]; head++; } } } } inline void query(node *root, int si, int sj, int di, int dj, int l, int c, char flag) { int i,j; node *p = root; int x; for (i=si, j=sj; i>=0 && i<l && j>=0 && j<c; i+=di, j+=dj) { x = map[i][j] - 'A'; while(p->next[x]==NULL && p!=root) p = p->fail; p = p->next[x]; p = (p==NULL)?root:p; if (p->len!=0 && sign[p->w]==0) { record[p->w][0] = i - (p->len-1)*di; record[p->w][1] = j - (p->len-1)*dj; sign[p->w] = flag; } } } int main() { int l, c, w; int i,k; int dir[2][8] = {{-1,-1,0,1,1,1,0,-1},{0,1,1,1,0,-1,-1,-1}}; scanf("%d %d %d", &l, &c, &w); for (i=0; i<l; i++) scanf("%s", map[i]); total = 0; root = new_node(); for (i=0; i<w; i++) { scanf("%s", word); insert(root, word, i); } build_ac_automation(root); memset(sign, 0, sizeof(sign)); for (i=0; i<l; i++) { k = 2; query(root, i, 0, dir[0][k], dir[1][k], l, c, 'A'+k); k = 6; query(root, i, c-1, dir[0][k], dir[1][k], l, c, 'A'+k); } for (i=0; i<c; i++) { k = 4; query(root, 0, i, dir[0][k], dir[1][k], l, c, 'A'+k); k = 0; query(root, l-1, i, dir[0][k], dir[1][k], l, c, 'A'+k); } for (i=0; i<c; i++) { k = 1; query(root, l-1, i, dir[0][k], dir[1][k], l, c, 'A'+k); query(root, i, 0, dir[0][k], dir[1][k], l, c, 'A'+k); k = 3; query(root, 0, i, dir[0][k], dir[1][k], l, c, 'A'+k); query(root, i, 0, dir[0][k], dir[1][k], l, c, 'A'+k); k = 5; query(root, 0, i, dir[0][k], dir[1][k], l, c, 'A'+k); query(root, i, c-1, dir[0][k], dir[1][k], l, c, 'A'+k); k = 7; query(root, l-1, i, dir[0][k], dir[1][k], l, c, 'A'+k); query(root, i, c-1, dir[0][k], dir[1][k], l, c, 'A'+k); } for (i=0; i<w; i++) { printf("%d %d %c/n", record[i][0], record[i][1], sign[i]); } return 0; }
【P.S】
其实我觉得这份代码还是挺长的,有时候都没有勇气写这种代码。
但是,这毕竟是一条必经之路,如果想取得好成绩的话。
在以后,这种长度的代码也许是很常见的。
无论如何,加油!