实在是高!!!
怕以后看不懂但是也说不太清,就说一些点:
image
构建出来的的图其实是为了或者说可以看成为了后缀而服务的,回跳边是回跳到了后缀相同的地方。
即:你可以看图中7号点他的回跳边是3,你再从3往上走到0点就会发现这条路径只是从7回到零点的后缀。
image
然后查找过程:
看图有一次沿着5,6,7找即s,h,e然后根据回跳边跳转。
那么转移边有什么用呢,图里也说了主串沿着树边或者转移边走。
举个例子:串yasherhshe,遍历到yashe后,下一个字符是r,怎么走到r呢,他其实是在原来遍历e的时候i=7,然后在7的地方走转移边到r。
这样子就能直接找到后缀相同同时结尾为r的位置。
附上代码(直接copy董晓老师的):

#include<bits/stdc++.h>
using namespace std;
const int N=1000010;
int n;
char str[1000010];
int ch[N][26],cnt[N],idx;
int ne[N];
void insert(char *s){//注意这只是创建单个字符串的字典树
	int p=0;
	for(int i=0;s[i];i++){
		int j=s[i]-'a';
		if(!ch[p][j])ch[p][j]=++idx;//若子节点不存在那么就添加这个节点
		p=ch[p][j];
	}
	cnt[p]++;
}
void build(){
	queue<int>q;
	for(int i=0;i<26;i++){
		if(ch[0][i])q.push(ch[0][i]);
	}
	while(q.size()){
		int u=q.front();q.pop();
		for(int i=0;i<26;i++){
			int v=ch[u][i];
			if(v)ne[v]=ch[ne[u]][i],q.push(v);//儿子存在,创建回跳边
			else ch[u][i]=ch[ne[u]][i];//儿子不存在建立转移边
		}
		
	}
}
int query(char *s){
	int ans=0;
	for(int k=0,i=0;s[k];k++){
		i=ch[i][s[k]-'a'];
		for(int j=i;j&&~cnt[j];j=ne[j])
			ans+=cnt[j],cnt[j]=-1;
	}
	return ans;
}
int main(){
	cin>>n;
	for(int i=0;i<n;i++)
		cin>>str,insert(str);
	build();
	cin>>str;
	cout<<query(str)<<endl;
	return 0;
}