BZOJ4327: JSOI2012 玄武密碼
Description
在美麗的玄武湖畔,雞鳴寺邊,雞籠山前,有一塊富饒而秀美的土地,人們喚作進香河。相傳一日,一縷紫氣從天而至,只一瞬間便消失在了進香河中。老人們說,這是玄武神靈將天書藏匿在此。
很多年後,人們終於在進香河地區發現了帶有玄武密碼的文字。更加神奇的是,這份帶有玄武密碼的文字,與玄武湖南岸臺城的結構有微妙的關聯。於是,漫長的破譯工作開始了。
經過分析,我們可以用東南西北四個方向來描述臺城城磚的擺放,不妨用一個長度為N的序列來描述,序列中的元素分別是‘E’,‘S’,‘W’,‘N’,代表了東南西北四向,我們稱之為母串。而神秘的玄武密碼是由四象的圖案描述而成的M段文字。這裏的四象,分別是東之青龍,西之白虎,南之朱雀,北之玄武,對東南西北四向相對應。
現在,考古工作者遇到了一個難題。對於每一段文字,其前綴在母串上的最大匹配長度是多少呢?
Input
第一行有兩個整數,N和M,分別表示母串的長度和文字段的個數。
第二行是一個長度為N的字符串,所有字符都滿足是E,S,W和N中的一個。
之後M行,每行有一個字符串,描述了一段帶有玄武密碼的文字。依然滿足,所有字符都滿足是E,S,W和N中的一個。
Output
輸出有M行,對應M段文字。
每一行輸出一個數,表示這一段文字的前綴與母串的最大匹配串長度。
Sample Input
7 3
SNNSSNS
NNSS
NNN
WSEE
Sample Output
4
2
0
HINT
對於100%的數據,N<=10^7,M<=10^5,每一段文字的長度<=100。
應上傳者要求,此題不公開,如有異議,請提出.
Solution
自己想了一個做法,然後過是過了,就是跑的很慢,在BZOJ裏面排到最後一頁...
上網找了一下沒人寫的和我一樣。
大概思路就是
把模式串全部插進去後AC自動機後,對每個節點都開個vector存有多少個模式串經過這裏以及分別為多長的前綴,匹配的時候每次匹配完一個節點就直接把那個vector清空了就好,那這樣每個節點就只會訪問一次了,但是就是有點慢...
/************************************************************** Problem: 4327 User: henryy Language: C++ Result: Accepted Time:7596 ms Memory:175108 kb ****************************************************************/ #include <bits/stdc++.h> using namespace std; #define ll long long #define inf 0x3f3f3f3f #define N 100010 int fail[N*5], ch[N*5][5], last[N*5], tot; int n, m; char s1[(int)(1e7)+1]; vector<int>val[N*5][2]; int idx(char c) { if(c == 'E') return 0; if(c == 'S') return 1; if(c == 'W') return 2; if(c == 'N') return 3; } void insert(char *s, int id) { int u = 0; for(int i = 1; s[i]; ++i) { int c = idx(s[i]); if(!ch[u][c]) ch[u][c] = ++tot; u = ch[u][c]; val[u][0].push_back(id); val[u][1].push_back(i); } } int q[N]; void get_fail() { int l = 1, r = 1; for(int i = 0; i < 4; ++i) if(ch[0][i]) q[r++] = ch[0][i]; while(l != r) { int u = q[l++]; if(l == 100000) l = 1; for(int i = 0; i < 4; ++i) { if(ch[u][i]) { fail[ch[u][i]] = ch[fail[u]][i]; q[r++] = ch[u][i]; if(r == 100000) r = 1; } else ch[u][i] = ch[fail[u]][i]; } } } int ans[N]; void query(char *s) { int u = 0; for(int i = 1; s[i]; ++i) { u = ch[u][idx(s[i])]; for(int j = u; j && val[j][0].size(); j = fail[j]) { if(val[j][0].size()) { for(int k = 0, len = val[j][0].size(); k < len; ++k) { ans[val[j][0][k]] = max(ans[val[j][0][k]], val[j][1][k]); } val[j][0].clear(); val[j][1].clear(); } } } } char s[N]; int main() { scanf("%d%d", &n, &m); scanf("%s", s1 + 1); for(int i = 1; i <= m; ++i) { scanf("%s", s + 1); insert(s, i); } get_fail(); query(s1); for(int i = 1; i <= m; ++i) { printf("%d\n", ans[i]); } return 0; }
並去學習了一下網上的做法:其實只要在AC自動機上面打標記就好了,然後最後遍歷所有串去找最大的標記處(因為每次跑失配指針其實都是因為前綴能夠匹配的上)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 300010
int fail[N*5], ch[N*5][5], val[N*5], tot;
int n, m, v[N*5];
char s1[(int)(1e7)+1];
int idx(char c) {
if(c == 'E') return 0;
if(c == 'S') return 1;
if(c == 'W') return 2;
return 3;
}
void insert(char *s, int id) {
int u = 0;
for(int i = 1; s[i]; ++i) {
int c = idx(s[i]);
if(!ch[u][c]) ch[u][c] = ++tot;
u = ch[u][c];
}
val[id] = u;
}
int q[N];
void get_fail() {
int l = 1, r = 1;
for(int i = 0; i < 4; ++i)
if(ch[0][i]) q[r++] = ch[0][i];
while(l != r) {
int u = q[l++];
if(l == 100000) l = 1;
for(int i = 0; i < 4; ++i) {
if(ch[u][i]) {
fail[ch[u][i]] = ch[fail[u]][i];
q[r++] = ch[u][i];
if(r == 100000) r = 1;
} else ch[u][i] = ch[fail[u]][i];
}
}
}
void query(char *s) {
int u = 0;
for(int i = 1; s[i]; ++i) {
u = ch[u][idx(s[i])];
for(int j = u; j && !v[j]; j = fail[j]) {
v[j] = 1;
}
}
}
int ask(char *s) {
int ans = 0;
for(int u = 0, i = 1; s[i]; i++) {
u = ch[u][idx(s[i])];
if(v[u]) ans = max(ans, i);
}
return ans;
}
char s[N][120];
int main() {
scanf("%d%d", &n, &m);
scanf("%s", s1 + 1);
for(int i = 1; i <= m; ++i) {
scanf("%s", s[i] + 1);
insert(s[i], i);
}
get_fail();
query(s1);
for(int i = 1; i <= m; ++i) {
printf("%d\n", ask(s[i]));
}
return 0;
}
BZOJ4327: JSOI2012 玄武密碼