4327: JSOI2012 玄武密码
4327: 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
SNNSSNS
NNSS
NNN
WSEE
Sample Output
4
2
0
2
0
HINT
对于100%的数据,N<=10^7,M<=10^5,每一段文字的长度<=100。
这题虽说是板子,但也不能只用板子,是一道比较不错的AC自动机题目。首先发现多模式串,二话不说上AC自动机,但是发现只有‘E’、‘S’、‘W’、‘N’这四个字母,所以可以优化一下建立起从‘E’、‘S’、‘W’、‘N’到‘a’、‘b’、‘c’、‘d’的映射就好了,然后要查询每个串的前缀与母串的最大匹配长度,那我们就在trie树上标记母串所有的可以匹配到的位置,然后对于每一个模式串,我们就从它的trie树上进行检索,当发现第一个没有被母串匹配到的结点时,当前长度就是最大的匹配长度。另外还有一个小小的优化,如果说对于一个节点它已经被标记过了,那么它的next链上的所有点一定也被标记了,所以这时就可以直接break。
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 #include<map> 7 #include<queue> 8 #include<stack> 9 #include<algorithm> 10 #include<vector> 11 #define man 100005 12 #define maxn 10000005 13 using namespace std; 14 15 inline int read() 16 { 17 char c=getchar(); 18 int res=0,x=1; 19 while(c<'0'||c>'9') 20 { 21 if(c=='-') 22 x=-1; 23 c=getchar(); 24 } 25 while(c>='0'&&c<='9') 26 { 27 res=res*10+(c-'0'); 28 c=getchar(); 29 } 30 return res*x; 31 } 32 33 int n,m,tot=1; 34 int tree[maxn][5],nt[maxn],bo[maxn],f[maxn]; 35 char a[maxn],d[4],b[man][105]; 36 queue<int>q; 37 38 char pd(char c) 39 { 40 if(c=='E') return d[0]; 41 else if(c=='S') return d[1]; 42 else if(c=='W') return d[2]; 43 else return d[3]; 44 } 45 46 void trie(char *s) 47 { 48 int len=strlen(s),u=1; 49 for(register int i=0;i<len;i++) 50 { 51 int c=s[i]-'a'; 52 if(!tree[u][c]) 53 tree[u][c]=++tot; 54 u=tree[u][c]; 55 } 56 bo[u]=1; 57 } 58 59 void bfs() 60 { 61 for(register int i=0;i<=3;i++) 62 tree[0][i]=1; 63 nt[1]=0;q.push(1); 64 while(q.size()) 65 { 66 int u=q.front();q.pop(); 67 for(register int i=0;i<=3;i++) 68 { 69 if(!tree[u][i]) 70 tree[u][i]=tree[nt[u]][i]; 71 else 72 { 73 int v=tree[u][i]; 74 q.push(v); 75 nt[v]=tree[nt[u]][i]; 76 } 77 } 78 } 79 } 80 81 void find(char *s) 82 { 83 int len=strlen(s),u=1,k; 84 for(register int i=0;i<len;i++) 85 { 86 int c=s[i]-'a'; 87 k=tree[u][c]; 88 while(k>1) 89 { 90 if(f[k]) break;//小小的优化 原理就是这个节点被标记过时,它的 91 f[k]=1;//next链上的点也一点被标记了,所以没必要再跳一次next 92 k=nt[k];//链,直接break掉就好了。 93 } 94 u=tree[u][c]; 95 } 96 } 97 98 int ask(char *s) 99 { 100 int len=strlen(s),u=1; 101 for(register int i=0;i<len;i++) 102 { 103 int c=s[i]-'a'; 104 u=tree[u][c]; 105 if(!f[u]) return i; 106 } 107 return len; 108 } 109 110 int main() 111 { 112 n=read();m=read(); 113 d[0]='a';d[1]='b';d[2]='c';d[3]='d'; 114 scanf("%s",a); 115 for(register int i=0;i<n;i++) 116 { 117 a[i]=pd(a[i]); 118 } 119 for(register int i=1;i<=m;i++) 120 { 121 scanf("%s",b[i]); 122 int len=strlen(b[i]); 123 for(register int j=0;j<len;j++) 124 { 125 b[i][j]=pd(b[i][j]); 126 } 127 trie(b[i]); 128 } 129 bfs(); 130 find(a); 131 for(register int i=1;i<=m;i++) 132 { 133 printf("%d\n",ask(b[i])); 134 } 135 return 0; 136 }