ac自动机中,如果以trie中的节点为节点,(fail[i],i)为边,可以建立一颗树,该树有如下特点:“节点u是节点v的祖先 当且仅当 u代表的字符串是v代表的字符串的一个后缀”。(u代表的字符串是由根节点到u路径上所有的边代表的字符顺次组合成的,我们记作str(u))。
本题中的每一个P都对应trie中的一个节点,所以本题就是求str(b)中有多少个str(a)子串:
如果len(str(b))<len(str(a)),则为0
如果len(str(b))==len(str(a)),则判断a和b是否是同一个字符串。
如果len(str(b))<len(str(a)),则str(a)一定是str(b)一个前缀的后缀,str(b)的前缀就是根到b路径上所有点代表的字符串,而如果str(a)是str(b)的一个后缀,则在fail树中,b一定在a的子树中。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 /************************************************************** 2 Problem: 2434 3 User: idy002 4 Language: C++ 5 Result: Accepted 6 Time:580 ms 7 Memory:18912 kb 8 ****************************************************************/ 9 10 #include <cstdio> 11 #include <cstring> 12 #include <queue> 13 #define maxn 100010 14 #define fprintf(...) 15 using namespace std; 16 17 struct Query { 18 int a, id; 19 Query( int a, int id ):a(a),id(id){} 20 }; 21 22 int n, m, ans[maxn]; 23 char str[maxn]; 24 int son[maxn][26], pre[maxn], fail[maxn], ppos[maxn], ntot; 25 int ww[maxn], dl[maxn], dr[maxn], dfs_clock; 26 vector<int> g[maxn]; 27 vector<Query> qry[maxn]; 28 29 30 void modify( int pos, int delta ) { 31 for( int i=pos; i<=n; i+=i&-i ) 32 ww[i] += delta; 33 } 34 int query( int pos ) { 35 int rt=0; 36 for( int i=pos; i; i-=i&-i ) 37 rt += ww[i]; 38 return rt; 39 } 40 void build_trie( int n, const char *P ) { 41 int u = 0; 42 int pid_clock=0; 43 for( int i=0; i<n; i++ ) { 44 if( P[i]=='P' ) { 45 pid_clock++; 46 ppos[pid_clock] = u; 47 fprintf( stderr, "the No.%d P is at node %d\n", pid_clock, u ); 48 } else if( P[i]=='B' ) { 49 u = pre[u]; 50 fprintf( stderr, "up to %d\n", u ); 51 } else { 52 int c=P[i]-'a'; 53 if( !son[u][c] ) { 54 ntot++; 55 son[u][c] = ntot; 56 pre[ntot] = u; 57 } 58 fprintf( stderr, "%d->%d with %c\n", u, son[u][c], c+'a' ); 59 u = son[u][c]; 60 } 61 } 62 } 63 void build_fail() { 64 queue<int> qu; 65 for( int c=0; c<26; c++ ) { 66 int v=son[0][c]; 67 if( !v ) continue; 68 fail[v] = 0; 69 fprintf( stderr, "fail[%d] = %d\n", v, fail[v] ); 70 g[0].push_back(v); 71 qu.push( v ); 72 } 73 while( !qu.empty() ) { 74 int u=qu.front(); 75 qu.pop(); 76 for( int c=0; c<26; c++ ) { 77 int v=son[u][c]; 78 int w=fail[u]; 79 if( !v ) continue; 80 while( w && !son[w][c] ) w=fail[w]; 81 fail[v] = son[w][c]; 82 fprintf( stderr, "fail[%d] = %d\n", v, fail[v] ); 83 g[son[w][c]].push_back( v ); 84 qu.push( v ); 85 } 86 } 87 } 88 void dfs( int u ) { 89 dl[u] = ++dfs_clock; 90 fprintf( stderr, "(%d ", u ); 91 for( int t=0; t<g[u].size(); t++ ) 92 dfs( g[u][t] ); 93 dr[u] = dfs_clock; 94 fprintf( stderr, ")" ); 95 } 96 void solve( int n, const char *P ) { 97 int u=0; 98 for( int i=0; i<n; i++ ) { 99 if( P[i]=='P' ) { 100 for( int t=0; t<qry[u].size(); t++ ) { 101 Query &q = qry[u][t]; 102 if( q.a==u ) ans[q.id]=1; 103 else ans[q.id] = query(dr[q.a])-query(dl[q.a]-1); 104 } 105 } else if( P[i]=='B' ) { 106 if( u==0 ) continue; 107 modify( dl[u], -1 ); 108 u = pre[u]; 109 } else { 110 int c=P[i]-'a'; 111 u = son[u][c]; 112 modify( dl[u], +1 ); 113 } 114 } 115 } 116 int main() { 117 scanf( "%s", str ); 118 n = strlen(str); 119 build_trie(n,str); 120 build_fail(); 121 dfs(0); 122 fprintf( stderr, "\n" ); 123 scanf( "%d", &m ); 124 for( int i=1,a,b; i<=m; i++ ) { 125 scanf( "%d%d", &a, &b ); 126 a = ppos[a]; 127 b = ppos[b]; 128 qry[b].push_back( Query(a,i) ); 129 } 130 solve(n,str); 131 for( int i=1; i<=m; i++ ) 132 printf( "%d\n", ans[i] ); 133 } 134
收获:
1、“a是b的子串 当且仅当 a是b的后缀的前缀或前缀的后缀“ 所以统计时就可以根据这个分类判断。
2、 fail指针对应的树的含义:同一条链上,深度小的字符串是深度大的字符串的后缀。