题目链接:https://www.luogu.org/problem/P3796
这题相比简单版就稍微调整一下,用ans数组存储单词出现的次数,然后最后比较次数大小输出次数最多的那几个字符串就行了,注意的是这题不用记录点是否被访问过,因为要统计出现的次数。
#include<iostream>
#include<cstring>
#include<string>
#include<queue>
using namespace std;
const int maxn=1e6+10;
int cnt=0;
int ans[maxn];
int trie[maxn][26];//trie[i][j]表示的当前字母为i+'a',且父节点编号为i的节点的编号
int cntword[maxn];
int fail[maxn];
void insert(string s,int u)
{
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]=u;//当前节点的单词数+1,而不是等于1,因为可能重复。
//这时的root是单词的末尾的编号,表示的是一整个单词,途中经历过的点的cntword是没有自增的;
}
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]);
}
while(!q.empty())
{
int now=q.front();
q.pop();
for(int i=0;i<26;i++)
{
if(trie[now][i])//通过枚举26个字母来找到now的子节点们
{
//该节点i的失败指针指向它父节点now的失败指针fail[now]的和i节点相同的节点
fail[trie[now][i]]=trie[fail[now]][i];
q.push(trie[now][i]);
}
else
trie[now][i]=trie[fail[now]][i];//如果不存在这个节点就上跳到失配节点的等于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&&cntword[j]!=-1;j=fail[j])//当前节点匹配完成或失配时就上跳fail指针
{
ans[cntword[j]]++;//只有j是某个单词末尾时才有值
//cntword[j]=-1;//记录过的节点要标记,防止重复
}
}
}
int main()
{
int n;
while(cin>>n&&n)
{
cnt=0;
memset(cntword,0,sizeof(cntword));
memset(fail,0,sizeof(fail));
memset(trie,0,sizeof(trie));
memset(ans,0,sizeof(ans));
string a[155],s;
for(int i=1;i<=n;i++)
{
cin>>a[i];
insert(a[i],i);
}
getFail();
cin>>s;
query(s);
int maxnum=-0x7fffff;
for(int i=1;i<=n;i++)
maxnum=max(maxnum,ans[i]);
cout<<maxnum<<endl;
for(int i=1;i<=n;i++)
if(ans[i]==maxnum)
cout<<a[i]<<endl;
}
return 0;
}