【題解】[JSOI2012]玄武密碼
阿新 • • 發佈:2022-03-29
題目連結
題意
輸出模式串在文字串中能夠匹配上的最長字首。
輸入格式
第一行有兩個整數,\(N\)和\(M\),分別表示母串的長度和文欄位的個數;
第二行是一個長度為\(N\)的字串,所有字元都滿足是 E,S,W 和 N 中的一個;
之後\(M\)行,每行有一個字串,描述了一段帶有玄武密碼的文字。依然滿足,所有字元都滿足是 E,S,W 和 N 中的一個。
輸出格式
輸出有\(M\)行,對應\(M\)段文字。
每一行輸出一個數,表示這一段文字的字首與母串的最大匹配串長度。
思路
這是一道披上辣椒衣的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自動機輸入資料的順序不同,或許提示著一種更自由地對待模式串的方式(指最後遍歷一遍)。