题目背景
在美丽的玄武湖畔,鸡鸣寺边,鸡笼山前,有一块富饶而秀美的土地,人们唤作进香河。相传一日,一缕紫气从天而至,只一瞬间便消失在了进香河中。老人们说,这是玄武神灵将天书藏匿在此。
很多年后,人们终于在进香河地区发现了带有玄武密码的文字。更加神奇的是,这份带有玄武密码的文字,与玄武湖南岸台城的结构有微妙的关联。于是,漫长的破译工作开始了。
题目描述
经过分析,我们可以用东南西北四个方向来描述台城城砖的摆放,不妨用一个长度为 n 的序列 s 来描述,序列中的元素分别是 E,S,W,N,代表了东南西北四向,我们称之为母串。而神秘的玄武密码是由四象的图案描述而成的 m 段文字。这里的四象,分别是东之青龙,西之白虎,南之朱雀,北之玄武,对东南西北四向相对应。
现在,考古工作者遇到了一个难题。对于每一段文字 t,求出其最长的前缀 p,满足 p 是 s 的子串。
输入格式
第一行有两个整数,分别表示母串的长度 n 和文字段的个数 m。
第二行有一个长度为 n 的字符串,表示母串 s。
接下来 m 行,每行一个字符串,表示一段带有玄武密码的文字 t。
输出格式
对于每段文字,输出一行一个整数,表示最长的 p 的长度。
输入输出样例
输入
7 3
SNNSSNS
NNSS
NNN
WSEE
输出
4
2
0
Solution
把所有询问建一个trie树,求出fail,把母串在trie树上跑一遍,标记下路径,从每个询问的最后一个点向上找答案即可。
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<queue>
using namespace std;
const int N=1e7+10;
map<char,int>mp;
char s[N],t[110];
int trie[N][4],tot,fail[N],ans[N];
int pos[N],fa[N],flag[N];
void ins(char s[],int x)
{
int len=strlen(s),c=0;
for(int i=0;i<len;i++)
{
int n=mp[s[i]];
if(!trie[c][n]) trie[c][n]=++tot;
fa[trie[c][n]]=c;
c=trie[c][n];
ans[c]=i+1;
}
pos[x]=c;
}
queue<int>que;
void build()
{
for(int i=0;i<4;i++)
if(trie[0][i]) que.push(trie[0][i]);
while(!que.empty())
{
int c=que.front();
que.pop();
for(int i=0;i<4;i++)
{
if(trie[c][i]) fail[trie[c][i]]=trie[fail[c]][i],que.push(trie[c][i]);
else trie[c][i]=trie[fail[c]][i];
}
}
}
void qry(char s[],int len)
{
int c=0;
for(int i=0;i<len;i++)
{
int n=mp[s[i]];
c=trie[c][n];
for(int j=c;j;j=fail[j]) flag[j]=1;
}
}
int Find(int x)
{
int res;
if(!x) res=0;
else if(flag[x]) res=ans[x];
else res=Find(fa[x]);
flag[x]=1;
return ans[x]=res;
}
int main()
{
mp[‘E‘]=0,mp[‘S‘]=1,mp[‘W‘]=2,mp[‘N‘]=3;
int n,m;
scanf("%d%d",&n,&m);
scanf("%s",s);
for(int i=1;i<=m;i++)
{
scanf("%s",t);
ins(t,i);
}
build();
qry(s,n);
for(int i=1;i<=m;i++)
printf("%d\n",Find(pos[i]));
return 0;
}