题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6096
题意:给出几组字符串W,又给出几个字符串的前缀p和后缀s,要求在前面给出的几组W字符串中找出前缀后缀同样出现p和s的数目
解题思路:
有两种,都很巧妙地拼凑出AC自动机解法
第一种:
把w字符串保存为w+’{’+w的形式,这样’{‘的左右两边就出现了后缀+{+前缀的情况,{用来分割开后缀与前缀,至于为什么用{,应该{的ASCII码值正好在z的后面一位,利用构造字典树。
然后将要寻找的前缀p和后缀s构造成s+{+p的形式,进行建树。
然后就在建好的字典树中利用ac自动机去寻找上面w的最大匹配数就行了
PS:①注意匹配成功的条件不仅仅是找到,还要求w的原长要大于等于p+s的 长度,以免出现aa aa匹配到了aaa的情况。同时要注意w的原长=(w的现在长度-1)/2,p+s长度为现在长度减1
②注意在构建字典树的时候,cntword[pos]不再是++了,而是直接令它=1,不然当出现两组p s相同的情况时候,会导致cntword[pos]=2,对于每一组的p s多加了1
第二种:
仍然是构造ac自动机的条件,将p和s依次插入字典树,也就是先放入p[0]再放入s[len-1],再放入p[1],然后放入s[len-2],构造出这种交错的情况,如果前缀和后缀数量不同,就用{来填充。
对于w串也同样采用这种方法构造,然后在字典树中寻找最大匹配就行。
第一种AC代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<queue>
#include<string.h>
using namespace std;
int t,n,q;
int cnt;
string w[100100],p,s;
int trie[3222200][27];
int cntword[3222200];
int fail[3222200];
int length[3222200];
int ans[3222200];
int pos[100100];
void init()
{
memset(trie,0,sizeof(trie));
memset(cntword,0,sizeof(cntword));
memset(fail,0,sizeof(fail));
memset(length,0,sizeof(length));
memset(ans,0,sizeof(ans));
cnt=0;
}
int insert(string str)
{
int len=str.size();
int pos=0;
for(int i=0;i<len;i++)
{
int next=str[i]-'a';
if(!trie[pos][next])
{
trie[pos][next]=++cnt;
length[cnt]=i+1;
}
pos=trie[pos][next];
}
cntword[pos]=1; //这里不再是++了,因为如果有两个q串相同,不应该加2,而仍应该加1
return pos;
}
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]);
}
int now;
while(!Q.empty())
{
now=Q.front(); Q.pop();
for(int i=0;i<=26;i++)
{
if(trie[now][i])
{
fail[trie[now][i]]=trie[fail[now]][i];
Q.push(trie[now][i]);
}
else
trie[now][i]=trie[fail[now]][i];
}
}
}
void query(string str)
{
int len=str.size();
int now=0;
for(int i=0;i<len;i++)
{
now=trie[now][str[i]-'a'];
for(int j=now;j;j=fail[j])
{
if(cntword[j]&&length[j]<=((len-1)/2+1))
{
ans[j]+=cntword[j];
}
//if(length[j]<=((len-1)/2+1)) //这里一定要注意匹配长度不应该是len,而应该是原长+一个‘{’
//{
// ans[j]++;
//}
}
}
}
int main()
{
scanf("%d",&t);
while(t--)
{
init();
scanf("%d%d",&n,&q);
for(int i=0;i<n;i++)
{
cin>>w[i];
string temp="{"; //这边一定要注意,用的符号要刚好大于z,不然runtime error,而且字典树范围不再在26,开到27
w[i]+=temp+w[i];
}
for(int i=0;i<q;i++)
{
cin>>p>>s;
string temp="{";
s+=temp+p;
pos[i]=insert(s);
}
getfail();
for(int i=0;i<n;i++)
query(w[i]);
for(int i=0;i<q;i++)
printf("%d\n",ans[pos[i]]);
}
return 0;
}
参考:https://blog.csdn.net/lxy767087094/article/details/77163275
https://blog.csdn.net/static_ricardo/article/details/77072865