题面
【题目描述】
在美丽的玄武湖畔,鸡鸣寺边,鸡笼山前,有一块富饶而秀美的土地,人们唤作进香河。相传一日,一缕紫气从天而至,只一瞬间便消失在了进香河中。老人们说,这是玄武神灵将天书藏匿在此。
很多年后,人们终于在进香河地区发现了带有玄武密码的文字。更加神奇的是,这份带有玄武密码的文字,与玄武湖南岸台城的结构有微妙的关联。于是,漫长的破译工作开始了。
经过分析,我们可以用东南西北四个方向来描述台城城砖的摆放,不妨用一个长度为
N
N
N 的序列来描述,序列中的元素分别是
E
,
S
,
W
,
N
E,S,W,N
E,S,W,N,代表了东南西北四向,我们称之为母串。而神秘的玄武密码是由四象的图案描述而成的
M
M
M 段文字。这里的四象,分别是东之青龙,西之白虎,南之朱雀,北之玄武,对东南西北四向相对应。
现在,考古工作者遇到了一个难题。对于每一段文字,其前缀在母串上的最大匹配长度是多少呢?
【输入】
第一行有两个整数,
N
N
N 和
M
M
M,分别表示母串的长度和文字段的个数;
第二行是一个长度为 N 的字符串,所有字符都满足是
E
,
S
,
W
和
N
E,S,W 和 N
E,S,W和N中的一个;
之后 M 行,每行有一个字符串,描述了一段带有玄武密码的文字。依然满足,所有字符都满足是 E,S,W 和 N 中的一个
【输出】
输出有 M 行,对应M 段文字。
每一行输出一个数,表示这一段文字的前缀与母串的最大匹配串长度。
【样例输入】
7 3
SNNSSNS
NNSS
NNN
WSEE
【样例输出】
4
2
0
【提示】
对于全部数据,
1
≤
N
≤
1
0
7
,
1
≤
M
≤
1
0
5
1≤N≤10^7,1≤M≤10^5
1≤N≤107,1≤M≤105,保证每一段文字的长度均小于等于
100
100
100。
算法分析
题目简单来说就是寻找
M
M
M个字符串在长度为
N
N
N的母串中出现的最长的长缀。
对
M
M
M个字符串建立
T
r
i
e
Trie
Trie字典树,使用
A
C
AC
AC自动机在树上进行匹配,在匹配是,对匹配到的所有点进行标记。
最后再在树上遍历一遍
M
M
M个字符,查看每个字符串中深度最大的标记,即为出现的最长的前缀长度。
时间复杂度:
O
(
1
0
7
)
O(10^7)
O(107)
参考程序
#include<bits/stdc++.h>
using namespace std;
const int N=10000010;
int trie[N][4],tot=1,vis[N],nxt[N];
int n,m;
char s[N],s2[100005][105];
queue<int> q;
int f(char ch)
{
if(ch=='E') return 0;
if(ch=='S') return 1;
if(ch=='W') return 2;
return 3;
}
void built(int k)
{
int len=strlen(s2[k]);
int u=1; //根节点
for(int i=0;i<len;i++)
{
int v=f(s2[k][i]);
if(trie[u][v]==0) trie[u][v]=++tot;//根节点为1,tot初始化一定设为1
u=trie[u][v];
}
vis[u]++; //结束标记
}
void bfs()
{
for(int i=0;i<=3;i++)//初始化
{
int v=trie[1][i];
if(v) nxt[v]=1,q.push(v); //根节点的孩子结点nxt指向根节点
else trie[1][i]=1; //优化,不存在直接又从1开始
}
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=0;i<=3;i++)
{
int v=trie[u][i];
if(v==0) trie[u][i]=trie[nxt[u]][i]; //优化,如果不存在则指向nxt[u]相同字符转移边,就不用再递归去找nxt[u]
else
{
q.push(v);
nxt[v]=trie[nxt[u]][i]; //存在,u的转移边结点v等于nxt[u]相同转移边的结点
}
}
}
}
void find()
{
int u=1;
int len=strlen(s);
for(int i=0;i<len;i++)
{
int v=f(s[i]);
u=trie[u][v]; //优化后不需要判断是否存在结点u的转移边
for(int j=u;j!=1&&vis[j]!=-1;j=nxt[j]) //统计,j为根节点或者已经统计过结束
{
vis[j]=-1; //标记出现过
}
}
}
int Find(int k)
{
int len=strlen(s2[k]);
int u=1; //根节点
int deep=0,i=0,ans=0;
while(i<len)
{
int v=f(s2[k][i]);
u=trie[u][v];
deep++;
i++;
if(vis[u]==-1) ans=deep; //记录深度最大的标记,即最长前缀
}
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
scanf("%s",s);
for(int i=1;i<=m;i++)
{
scanf("%s",s2[i]);
built(i);
}
bfs();
find();
for(int i=1;i<=m;i++)
printf("%d\n",Find(i));
return 0;
}