1 #include<iostream>
2 #include<algorithm>
3 #include<queue>
4 #include<string.h>
5 #include<stdlib.h>
6
7 using namespace std;
8
9 #define MAX_N 1000006
10 #define MAX_Tot 500005
11
12 struct ACo{
13 struct state{
14 //子節點數組
15 int next[26];
16 //當前節點的失敗指針
17 int fail;
18 //到當前位置的字符串結束個數
19 int cnt;
20
21 }stateTable[MAX_Tot];
22
23 //當前AC自動機樹的節點個數
24 int size;
25 queue<int> que;
26 //初始化
27 void init()
28 {
29 //將節點初始化
30 while(que.size()) que.pop();
31 for(int i=0;i<MAX_Tot;i++)
32 {
33 memset(stateTable[i].next,0,sizeof(stateTable[i].next));
34 stateTable[i].fail = 0;
35 stateTable[i].cnt = 0;
36 }
37 //根節點一定存在,所以節點個數爲1
38 size = 1;
39 }
40
41 //構建字典樹
42 void insert(char *str)
43 {
44 int n = strlen(str);
45 int now = 0;
46 for(int i=0;i<n;i++)
47 {
48 char c = str[i];
49 //如果到當前節點子節點不存在的話
50 if(!stateTable[now].next[c-'a'])
51 {
52 //開闢新節點,並將節點個數加一,注意size從1開始的
53 stateTable[now].next[c-'a'] = size++;
54 }
55 //每次都要進行走到下一節點
56 now = stateTable[now].next[c-'a'];
57
58 }
59 //該字符串便利完之後走到的節點
60 stateTable[now].cnt++;
61 }
62 //構造失配指針
63 void build()
64 {
65
66 //根節點的失配指針設爲-1
67 stateTable[0].fail = -1;
68 //將根節點壓入隊列
69 que.push(0);
70
71 while(que.size())
72 {
73 //取當前隊列中的第一個,即廣度優先遍歷數,保證每層節點的失敗指針都選擇完成,纔有可能繼續下一層
74 //否則,如果深度優先遍歷會導致指針不爲最優,因爲別的叉沒有被構造。
75 int u = que.front();
76 //取一個,要將其彈出
77 que.pop();
78
79 //將當前節點的所有子節點遍歷
80 for(int i=0;i<26;i++)
81 {
82 //如果當前節點的子節點之一存在子節點
83 if(stateTable[u].next[i])
84 {
85 //判斷當前點是否爲根節點
86 if(u==0)
87 {
88 //如果爲根節點,沒辦法,只能讓當前節點的子節點的失敗指針指向0,即指向根節點
89 //根節點的第一層節點都滿足,可以想一下。
90 stateTable[stateTable[u].next[i]].fail = 0;
91 }
92 //否則
93 else
94 {
95 //記錄當前節點的失敗指針
96 int v = stateTable[u].fail;
97 //如果失敗指針存在的話
98 while(v!=-1)
99 {
100 //並且其失敗指針節點也存在子節點
101 if(stateTable[v].next[i])
102 {
103 //將當前節點 的 失敗指針 指向其 失敗指針節點 的下一個節點
104 stateTable[stateTable[u].next[i]].fail = stateTable[v].next[i] ;
105 break;
106 }
107 //記錄下失敗指針的位置。
108 v = stateTable[v].fail;
109 }
110 //如果找了半天,其各種祖先節點的失敗指針仍然不存在
111 if(v==-1)
112 {
113 //只能將當前節點的失敗指針指向根節點
114 stateTable[stateTable[u].next[i]].fail = 0;
115 }
116 }
117 //將當前節點入隊列,畢竟要廣搜一層來確定
118 que.push(stateTable[u].next[i]);
119 }
120 }
121 }
122
123 }
124 int Get(int u)
125 {
126 int res = 0;
127 while(u)
128 {
129 //當前節點的不爲根節點
130 //res+上當前節點下的單詞數
131 res = res+stateTable[u].cnt;
132 //當前節點單詞數清零,避免一條子樹下重複加值
133 stateTable[u].cnt = 0;
134 //回溯其失敗指針下滿足條件的單詞數
135 u = stateTable[u].fail;
136 }
137 return res;
138 }
139 //製造匹配函數
140 int match(char *S)
141 {
142 int n = strlen(S);
143 int res = 0,now = 0;
144 for(int i=0;i<n;i++)
145 {
146 char c = S[i];
147 //存在自不必多說,向下一個指針移動
148 if(stateTable[now].next[c-'a'])
149 {
150 now = stateTable[now].next[c-'a'];
151 }
152 else
153 {
154 //一旦失配,不回溯根節點,而是回溯失敗指針節點
155 int p = stateTable[now].fail;
156 //如果失配指針存在,或者失配指針指向的根節點存在
157 while(p!=-1 && stateTable[p].next[c-'a']==0)
158 {
159 //更新失敗指針的位置,即不=停的向父節點靠攏
160 p = stateTable[p].fail;
161 }
162 //如果只能找到根節點,那就從根節點進行匹配
163 if(p==-1)
164 {
165 now = 0;
166 }
167 else{
168 //不然,當前節點跳到失敗節點的存在子節點
169 now = stateTable[p].next[c-'a'];
170 }
171 }
172 //如果當前節點下存在的單詞數不爲0
173 if(stateTable[now].cnt)
174 {
175 //記錄到當前節點爲止所有含有的父節點(失敗節點)的單詞數,
176 res +=Get(now);
177 }
178 }
179 return res;
180 }
181 }aho;
182 int T;
183 int n;
184 char S[MAX_N];
185
186 int main()
187 {
188 scanf("%d",&T);
189 while(T--)
190 {
191 aho.init();
192 scanf("%d",&n);
193 for(int i=0;i<n;i++)
194 {
195 scanf("%s",S);
196 aho.insert(S);
197 }
198 aho.build();
199 scanf("%s",S);
200 printf("%d\n",aho.match(S));
201 }
202 }