【题解】[JSOI2012] 玄武密码
题目链接
题意
输出模式串在文本串中能够匹配上的最长前缀。
输入格式
第一行有两个整数,和,分别表示母串的长度和文字段的个数;
第二行是一个长度为的字符串,所有字符都满足是 E,S,W 和 N 中的一个;
之后行,每行有一个字符串,描述了一段带有玄武密码的文字。依然满足,所有字符都满足是 E,S,W 和 N 中的一个。
输出格式
输出有行,对应段文字。
每一行输出一个数,表示这一段文字的前缀与母串的最大匹配串长度。
思路
这是一道披上辣椒衣的AC自动机模板题。难点就在这层辣椒衣。
解决方法:先打标记,最后遍历。
代码实现
#include<cstdio>
#include<queue>
#include<cstring>
#include<map>
using namespace std;
const int N=1e5+5;
int n,m;
int t[N*100][5],cnt,fail[N*100];
char s[N][105],txt[N*100];
bool vis[N*100];
map <char,int> mp;
inline int ans(int x){
int p=0,ret=0;
int l=strlen(s[x]);
for(int i=0;i<l;i++){
int c=mp[s[x][i]];
p=t[p][c];
if(vis[p]) ret=i+1;
}
return ret;
}
inline void query(char x[]) {
int p=0;
for(int i=0; i<n; i++) {
p=t[p][mp[x[i]]];
int j=p;
while(j && (!vis[j])){
vis[j]=true;
j=fail[j];
}
}
}
inline void build_graph() {
queue <int> q;
for(int i=0; i<4; i++) {
if(t[0][i]) q.push(t[0][i]);
}
while(!q.empty()) {
int p=q.front();
q.pop();
for(int i=0; i<4; i++) {
if(t[p][i]) {
q.push(t[p][i]);
fail[t[p][i]]=t[fail[p]][i];
} else t[p][i]=t[fail[p]][i];
}
}
}
inline void ins(char x[]) {
int l=strlen(x);
int p=0;
for(int i=0; i<l; i++) {
int c=mp[x[i]];
if(!t[p][c]) t[p][c]=++cnt;
p=t[p][c];
}
}
int main() {
mp['E']=0; mp['S']=1; mp['W']=2; mp['N']=3;
scanf("%d%d",&n,&m);
scanf(" %s",txt);
for(int i=1; i<=m; i++) {
scanf(" %s",s[i]);
ins(s[i]);
}
build_graph();
query(txt);
for(int i=1; i<=m; i++) printf("%d\n",ans(i));
return 0;
}
PS. 为了体验不能用万能头的感觉,没有用万能头。
反思:
必须承认,看了题解才写出看上去能算优美的代码。
一开始想要直接把trie的节点弄成包罗万象的大结点,就是说,把每个结点弄成大struct
,用每个结点映射它对应的模式串,包括记录它的深度,etc。然后发现写起来很头大。
看了一篇题解,发现可以通过先打标记,最后遍历的方法来确定能匹配的前缀长度。
这是一种存储数据解题(离线)的思维。
事实上,题目提示得很明显:先输入文本串,后输入模式串。这和一般AC自动机输入数据的顺序不同,或许提示着一种更自由地对待模式串的方式(指最后遍历一遍)。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】