第一道AC自动机题目。
记一下对AC自动机的理解吧:
AC自动机=Trie+KMP。即在Trie上应用KMP思想,实现多Pattern的匹配问题。
复杂度是预处理O(segma len(P)),匹配是O(len(T))。应该也是下界了。
它预处理做了以下事情:
1、建立所有Pattern的Trie
2、计算出fail和last数组
匹配时和KMP很像。
我对fail和last的理解:
对于一棵Trie,上面的一个节点对应一个字符串,该字符串是root到该节点的路径上,边代表的字符连接起来的。
fail[i]是一个指针,它指向一个节点f,使得f代表的字符串是i代表的字符串最长的一个后缀(所以f的深度一定比i小)。
last[i]也是一个指针,它指向一个节点f,使得f代表的字符串是i代表的字符串的一个后缀且该后缀也是一个Pattern。(如果不存在则指向根)。
我们将根节点代表的字符串理解成空串,空串是任何字符串的子串。
对于匹配过程,我们将Text串也想成一个由点和边组成的链,我们从最左边的点开始向右开始匹配。
我们以Text串的位置为阶段进行匹配,如果Trie的当前节点存在一条边,使得它代表的字符串和text串一致,就两边都前进。如果不存在,trie中的节点就不断通过fail向上跳,直到存在边或到达根,如果存在边就同时向下走,否则(即到达根且根也没有对应边),那就trie保持在根,text串到下一节点。
然后每到一个新的trie节点就查看是否匹配到(要用到last数组).
去重的几种方式:
1、set
2、sort+unique
3、mark+vector
各有特点,
set是实时添加和查询,O(nlogn)。
sort+unique是添加完后查询(多用于离散话),也是O(nlogn)(常数比前者小)。
mark+vector,要求范围较小(10^7以内),O(n)
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 /************************************************************** 2 Problem: 2754 3 User: idy002 4 Language: C++ 5 Result: Accepted 6 Time:1320 ms 7 Memory:13264 kb 8 ****************************************************************/ 9 10 #include <cstdio> 11 #include <cctype> 12 #include <queue> 13 #include <vector> 14 #include <set> 15 #include <map> 16 #include <algorithm> 17 #define maxn 100010 18 using namespace std; 19 20 typedef vector<int> String; 21 22 int gint() { 23 int rt; 24 char ch, opt; 25 while( !isdigit(ch=getchar()) ) opt=ch; 26 rt=ch-'0'; 27 while( isdigit(ch=getchar()) ) rt=rt*10+ch-'0'; 28 return rt; 29 } 30 31 int n, m; 32 int ans[2][maxn]; 33 String str[maxn], ss; 34 bool mark[maxn]; 35 vector<int> in; 36 37 struct AC { 38 map<int,int> son[maxn]; 39 set<int> st; 40 vector<int> stk[maxn]; 41 int fail[maxn], last[maxn], ntot; 42 43 void insert( int id, String &p ) { 44 int u=0; 45 for( int i=0; i<p.size(); i++ ) { 46 int c=p[i]; 47 if( !son[u][c] ) son[u][c] = ++ntot; 48 u=son[u][c]; 49 } 50 stk[u].push_back(id); 51 } 52 void build() { 53 queue<int> qu; 54 for( map<int,int>::iterator it=son[0].begin(); it!=son[0].end(); ++it ) { 55 int v=it->second; 56 qu.push(v); 57 fail[v] = last[v] = 0; 58 } 59 while(!qu.empty()) { 60 int u=qu.front(); 61 qu.pop(); 62 for( map<int,int>::iterator it=son[u].begin(); it!=son[u].end(); ++it ) { 63 int c=it->first; 64 int v=it->second; 65 int w=fail[u]; 66 while( w && !son[w][c] ) w=fail[w]; 67 int x=son[w][c]; 68 fail[v] = x; 69 last[v] = stk[x].size() ? x : last[x]; 70 qu.push(v); 71 } 72 } 73 } 74 void add( int u ) { 75 if( !u ) return; 76 add(last[u]); 77 for( int t=0; t<stk[u].size(); t++ ) { 78 if( mark[stk[u][t]] ) continue; 79 mark[stk[u][t]] = true; 80 in.push_back( stk[u][t] ); 81 } 82 } 83 void search( int id, String &T ) { 84 st.clear(); 85 int u=0; 86 for( int i=0; i<T.size(); i++ ) { 87 while( u && !son[u][T[i]] ) u=fail[u]; 88 u=son[u][T[i]]; 89 if( stk[u].size() ) add(u); 90 else add(last[u]); 91 } 92 ans[0][id] += in.size(); 93 for( int t=0; t<in.size(); t++ ) { 94 ans[1][in[t]]++; 95 mark[in[t]]=false; 96 } 97 in.clear(); 98 } 99 }ac; 100 101 102 int main() { 103 n = gint(); 104 m = gint(); 105 for( int i=1,z; i<=n; i++ ) { 106 z = gint(); 107 while(z--) str[i].push_back( gint() ); 108 str[i].push_back(-1); 109 z = gint(); 110 while(z--) str[i].push_back( gint() ); 111 } 112 for( int i=1,z; i<=m; i++ ) { 113 ss.clear(); 114 z = gint(); 115 while(z--) ss.push_back( gint() ); 116 ac.insert( i, ss ); 117 } 118 ac.build(); 119 for( int i=1; i<=n; i++ ) 120 ac.search( i, str[i] ); 121 for( int i=1; i<=m; i++ ) 122 printf( "%d\n", ans[1][i] ); 123 for( int i=1; i<=n; i++ ) 124 printf( "%d%s", ans[0][i], i==n ? "" : " " ); 125 }