洛谷P5231 [JSOI2012]玄武密码

题目背景

在美丽的玄武湖畔,鸡鸣寺边,鸡笼山前,有一块富饶而秀美的土地,人们唤作进香河。相传一日,一缕紫气从天而至,只一瞬间便消失在了进香河中。老人们说,这是玄武神灵将天书藏匿在此。

很多年后,人们终于在进香河地区发现了带有玄武密码的文字。更加神奇的是,这份带有玄武密码的文字,与玄武湖南岸台城的结构有微妙的关联。于是,漫长的破译工作开始了。

题目描述

经过分析,我们可以用东南西北四个方向来描述台城城砖的摆放,不妨用一个长度为 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;
}
posted @ 2021-02-01 00:36  Acestar  阅读(117)  评论(0编辑  收藏  举报