https://www.luogu.org/problemnew/show/P3796
题目描述
有N个由小写字母组成的模式串以及一个文本串T。每个模式串可能会在文本串中出现多次。你需要找出哪些模式串在文本串T中出现的次数最多。
输入输出格式
输入格式:
输入含多组数据。
每组数据的第一行为一个正整数N,表示共有N个模式串,1≤N≤150。
接下去N行,每行一个长度小于等于70的模式串。下一行是一个长度小于等于10^6的文本串T。
输入结束标志为N=0。
输出格式:
对于每组数据,第一行输出模式串最多出现的次数,接下去若干行每行输出一个出现次数最多的模式串,按输入顺序排列。
输入输出样例
输入样例#1: 复制
2 aba bab ababababac 6 beta alpha haha delta dede tata dedeltalphahahahototatalpha 0
输出样例#1: 复制
4 aba 2 alpha haha
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2*1e5+9;
int trie[maxn][26]; //字典树
int cntword[maxn]; //记录该单词出现次数
int fail[maxn]; //失败时的回溯指针
int ans[maxn];
int cnt = 0,temp=0;
int n;
string s[maxn*10];
void insertWords(string s,int v){
int root = 0;
for(int i=0;i<s.size();i++){
int next = s[i] - 'a';
if(!trie[root][next])
trie[root][next] = ++cnt;
root = trie[root][next];
}
cntword[root]=v; //当前节点单词数+1
}
void getFail(){
queue <int>q;
for(int i=0;i<26;i++){ //将第二层所有出现了的字母扔进队列
if(trie[0][i]){
fail[trie[0][i]] = 0;
q.push(trie[0][i]);
}
}
//fail[now] ->当前节点now的失败指针指向的地方
////tire[now][i] -> 下一个字母为i+'a'的节点的下标为tire[now][i]
while(!q.empty()){
int now = q.front();
q.pop();
for(int i=0;i<26;i++){ //查询26个字母
if(trie[now][i]){
//如果有这个子节点为字母i+'a',则
//让这个节点的失败指针指向(((他父亲节点)的失败指针所指向的那个节点)的下一个节点)
//有点绕,为了方便理解特意加了括号
fail[trie[now][i]] = trie[fail[now]][i];
q.push(trie[now][i]);
}
else//否则就让当前节点的这个子节点
//指向当前节点fail指针的这个子节点
trie[now][i] = trie[fail[now]][i];
}
}
}
void query(string s){
int now=0;
for(int i=0;i<s.size();i++){
now=trie[now][s[i]-'a'];
for(int j=now;j;j=fail[j])ans[cntword[j]]++;
}
}
int main() {
while(cin >> n && n)
{
memset(cntword,0,sizeof(cntword));
memset(ans,0,sizeof(ans));
memset(trie,0,sizeof(trie));
memset(fail,0,sizeof(fail));
cnt=0;
for(int i=1;i<=n;i++){
cin >> s[i] ;
insertWords(s[i],i);
}
getFail();
string s1;
cin>>s1;
query(s1);
temp=0;
for(int i=1;i<=n;i++)
if(ans[i]>temp)
temp=ans[i];
cout<<temp<<"\n";
for(int i=1;i<=n;i++)
if(ans[i]==temp)
cout<<s[i]<<"\n";
}
return 0;
}