题目大意;原题链接
给定一个字符串矩阵和待查找的单词,可以朝8个不同的方向查找,输出待查找单词第一个字母在矩阵中出现的位置和该单词被查到的方向.
A~H代表8个不同的方向,A代表正北方向,其他依次以45度角的方向顺时针增加.
解题思路:
解法一:Trie树暴搜
因为不查询重复单词,所以dfs(int u,int i,int j,int k)函数中当已经查询到单词时,val[u]可以置零做标记.
#include<cstdio> #include<cstring> #define maxn 200010 using namespace std; bool vis[26]; int n,m,w,x,y,sz=1;//sz得为全局变量 char str[1010][1010],word[1010]; int val[maxn],out[1010][3]; int dir[8][2]={-1,0,-1,1,0,1,1,1,1,0,1,-1,0,-1,-1,-1};//顺时针 struct Trie { int next[26]; }trie[maxn]; void insert(char *s,int k) { int u=0,len=strlen(s); for(int i=0;i<len;i++){ int id=s[i]-'A'; if(!trie[u].next[id]) trie[u].next[id]=sz++; u=trie[u].next[id]; } val[u]=k;//u为结点编号,k为单词编号 } void dfs(int u,int i,int j,int k)//k记录方向 { if(u==0||i<0||i>=n||j<0||j>=m) return;//该语句之前放在if(val[u])条件之后,WA if(val[u]){ out[val[u]][0]=x; out[val[u]][1]=y; out[val[u]][2]=k; val[u]=0;//做标记,因为不查询重复单词 } int id=str[i+dir[k][0]][j+dir[k][1]]-'A'; dfs(trie[u].next[id],i+dir[k][0],j+dir[k][1],k);//必须得朝同一方向搜索 } int main() { scanf("%d%d%d",&n,&m,&w); for(int i=0;i<n;i++) scanf("%s",str[i]); for(int i=1;i<=w;i++){ scanf("%s",word); insert(word,i); vis[word[0]-'A']=1; } for(int i=0;i<n;i++){ for(int j=0;j<m;j++){ if(vis[str[i][j]-'A']){ for(int k=0;k<8;k++){ x=i,y=j;//记录下最初的位置,从Trie树根开始向下搜索 dfs(trie[0].next[str[i][j]-'A'],i,j,k); } } } } for(int i=1;i<=w;i++) printf("%d %d %c ",out[i][0],out[i][1],out[i][2]+'A'); return 0; }
解法二:AC自动机
#include<queue> #include<cstdio> #include<cstdlib> #include<cstring> #define inf 0x3f3f3f3f using namespace std; char str[1010][1010],word[1010]; int n,m,w; int dir[8][2]={-1,0,-1,1,0,1,1,1,1,0,1,-1,0,-1,-1,-1};//顺时针 char ch[9]="ABCDEFGH"; int pos[1010][3]; struct TrieNode{ int id;//记录第几个字符串 TrieNode *next[26],*fail; TrieNode(){ id=0,fail=0; memset(next,0,sizeof(next)); } }*root; void InsertNode(char *s,int id) { int len=strlen(s)-1; TrieNode *p=root; while(len>=0){//这里将s数组倒着插入字典树,方便匹配时记录匹配的终点即原串的起始点 if(!p->next[s[len]-'A']) p->next[s[len]-'A']=new TrieNode; p=p->next[s[len--]-'A']; } p->id=id;//id记录单词编号 } void Build_AC() { TrieNode *p,*next; queue<TrieNode* > que; que.push(root); while (!que.empty()){ p=que.front(); que.pop(); for(int i=0;i<26;i++){ if(p->next[i]){ if(p==root) p->next[i]->fail=root; else{ TrieNode *temp=p->fail; while(temp){ if(temp->next[i]){//temp始终代表next[i]的爸爸,有next[i]这个儿子 p->next[i]->fail=temp->next[i]; break;//寻找最长后缀 } temp=temp->fail; } if(!temp) p->next[i]->fail=root; } que.push(p->next[i]); } } } } void Query(int x,int y,int d,int di) { TrieNode *p=root,*next; while(x>=0&&y>=0&&x<n&&y<m){ while(p&&!p->next[str[x][y]-'A']) p=p->fail; if(!p) p=root; else p=p->next[str[x][y]-'A']; next=p; while(next!=root){ if(next->id){//记录原串被匹配的起始点 int k=next->id; if(pos[k][0]>x||(pos[k][0]==x&&pos[k][1]>y)){ pos[k][0]=x; pos[k][1]=y; pos[k][2]=di;//记录单词整体朝向 } } next=next->fail; } x+=dir[d][0]; y+=dir[d][1]; } } void Free(TrieNode *p) { for(int i=0;i<26;i++){ if(p->next[i]) Free(p->next[i]); } delete p; } int main(){ scanf("%d%d%d",&n,&m,&w); root=new TrieNode; for(int i=0;i<n;i++) scanf("%s",str[i]); for(int i=1;i<=w;i++){ scanf("%s",&word); InsertNode(word,i); pos[i][0]=pos[i][1]=inf; } Build_AC(); for(int i=0;i<m;i++){ Query(0,i,3,7),Query(n-1,i,7,3); Query(0,i,4,0),Query(n-1,i,0,4); Query(0,i,5,1),Query(n-1,i,1,5); }//从矩阵上下左右四条边枚举8个方向 for(int i=0;i<n;i++){ Query(i,0,1,5),Query(i,m-1,5,1); Query(i,0,2,6),Query(i,m-1,6,2); Query(i,0,3,7),Query(i,m-1,7,3); } for(int i=1;i<=w;i++) printf("%d %d %c ",pos[i][0],pos[i][1],ch[pos[i][2]]); Free(root); }
后缀数组倍增解法过一段时间补上