题意:给定一个n*m大小的二维词表(0<n,m<=1000),然后给出不多于1000个单词。要求给出单词在词表中的位置(首字母位置和旋转方向)
思路1:一开始想也没想就写了暴搜,果断TLE。之后瞄了一眼题解都说用trie或者ac自动机。就算这样还是没想出怎么用trie存词表。原来是用trie存待查单词,对于词表,仍然是暴搜。注意虽然题目中没给每个单词的长度,但是长度上限可以确定,也就是词表的长或宽的上限,即1000。(一开始还在想对角线最长,大概是1000倍的根2,犯傻逼了)
注意不加这句:p->id= 0的话就会wa,加上就ac。一开始我认为题目是special judge,所以加不加没有区别。实际上匹配了一个字符串,后面num就加一,所以如果有串重复匹配,那么到最后可能出现有串没有匹配上的情况,所以匹配完一个串,必须将其置零。
思路2:将待查单词建立DFA,然后在词表中从最长的串去匹配。注意算起始点的方法。
trie树:
#include <stdio.h> #include <string.h> #define N 1005 typedef struct node{ int id; struct node* next[26]; }node; node *alloc,*root,trie[1414000]; struct output{ int x,y,o; }res[N]; char s[N][N],t[1444]; int n,m,k; int orient[8][2] = {{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}}; void init(){ int i; alloc = trie; root = alloc++; root->id = 0; for(i = 0;i<26;i++) root->next[i] = NULL; } void insert(char* x,int id){ int i,j; node *p,*q; p = root; for(i = 0;x[i]!='\0';i++){ if(!p->next[x[i]-'A']) break; p = p->next[x[i]-'A']; } for(;x[i]!='\0';i++){ q = alloc++; q->id = 0; for(j = 0;j<26;j++) q->next[j] = NULL; p->next[x[i]-'A'] = q; p = q; } p->id = id; } int in(int x,int y){ return x>=0&&x<n&&y>=0&&y<m; } int search(int x,int y,int ori){ int num=0,xx = x,yy = y; node *p = root; while(in(x,y) && (p = p->next[s[x][y]-'A'])){ if(p->id){ num++; res[p->id].x = xx; res[p->id].y = yy; res[p->id].o = ori; p->id= 0;//不加这句就会wa;一开始以为题目是special judge啊,所以我觉得这句加不加无所谓。实际上匹配了一个字符串,后面num就加一,所以如果有串重复匹配,那么到最后可能出现有串没有匹配上的情况,所以匹配完一个串,必须将其置零 } x += orient[ori][0]; y += orient[ori][1]; } return num; } void brute(){ int i,j,p,num=0; for(i = 0;i<n;i++) for(j = 0;j<m;j++) for(p = 0;p<8;p++){ num += search(i,j,p); if(num == k) return; } } int main(){ int i; init(); scanf("%d %d %d",&n,&m,&k); for(i = 0;i<n;i++) scanf("%s",s[i]); for(i = 1;i<=k;i++){ scanf("%s",t); insert(t,i); } brute(); for(i = 1;i<=k;i++) printf("%d %d %c\n",res[i].x,res[i].y,res[i].o+'A'); return 0; }
思路2:AC自动机
#include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <cstdlib> using namespace std; int T,n,num,res,m,k; char s[1005][1005],w[1005]; int out[1005][3],len[1005]; int ori[8][2] = {{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}}; struct trie{ struct trie *next[26]; struct trie *pre; bool bad; int id; void init(){ memset(next, NULL, sizeof(next)); pre = NULL; bad = false; id = -1; } }t[1000005],*root; queue<struct trie*> q; void insert(char* s,struct trie* r,int id){//建立trie树 for(int i = 0;s[i];i++){ if(r->next[s[i]-'A'] == NULL) r->next[s[i]-'A'] = t+(++num); r = r->next[s[i]-'A']; } r->bad = true; r->id = id; } void buildDFA(){ //构造DFA int i; for(i = 0;i<26;i++) //t[0]是附加结点,方便建立DFA t[0].next[i] = t+1; t[1].pre = t; t[0].pre = NULL; q.push(t+1); while (!q.empty()) { //按照层次处理结点,所以需要队列 struct trie *now = q.front(),*p,*pre; q.pop(); for(i = 0;i<26;i++){ p = now->next[i]; //p是当前要处理的结点 if(p){ pre = now->pre; //前向(失败)结点,目的是找前向结点的next[i]结点 while(pre){ if(pre->next[i]){ //最往上找到t[0],那么t[0].next[i]一定为真,且为根节点 p->pre = pre->next[i]; if(pre->next[i]->bad) p->bad = true; break; } pre = pre->pre; } q.push(p); } } } } int check(int x,int y){ return x>=0&&x<n&&y>=0&&y<m; } void search(int x,int y,int oo){ int xx,yy; struct trie *p,*r; xx = x; yy = y; r = t+1; while(check(xx,yy)){ while(!r->next[s[xx][yy]-'A']) r = r->pre; r = r->next[s[xx][yy]-'A']; //往下走一格 p = r; while(p){ if(p->bad){ //有bad为真说明找到了一个子串 out[p->id][0] = xx - (len[p->id]-1)*ori[oo][0]; out[p->id][1] = yy - (len[p->id]-1)*ori[oo][1]; out[p->id][2] = oo; p->bad = false; } p = p->pre; } xx += ori[oo][0]; yy += ori[oo][1]; } } int main(){ int i; scanf("%d %d %d",&n,&m,&k); num = 1; for(i = 0;i<n;i++) scanf("%s",s[i]); for(i = 0;i<k;i++){ scanf("%s",w); insert(w,t+1,i); len[i] = strlen(w); } buildDFA(); for(i = 0;i<n;i++){ search(i,0,1); search(i,0,2); search(i,0,3); search(i,m-1,5); search(i,m-1,6); search(i,m-1,7); } for(i = 0;i<m;i++){ search(0,i,3); search(0,i,4); search(0,i,5); search(n-1,i,7); search(n-1,i,0); search(n-1,i,1); } for(i = 0;i<k;i++){ printf("%d %d %c\n",out[i][0],out[i][1],out[i][2]+'A'); } return 0; }