标签:AC自动机
原题链接
这道题看上去并不好做,但是如果你学过fail指针,再结合题目标题,一看就知道是用AC自动机。
题目中提及“找到第x个字符串和第y个字符串的连续公共子串同时也是某个字符串的前缀的串”,我们便可以利用fail指针来解题。
考虑到 fail指针的性质:假设有一个节点k,他的失败指针指向j。那么k,j满足这个性质:设root到j的距离为n,则从k之上的第n个节点到k所组成的长度为n的单词,与从root到j所组成的单词相同。
那么对于每一个询问,我们将字符串x和字符串y的每一个字符的fail指针指到过的节点分别用布尔数组记录下来。如果一个节点同时被两个字符串中的字符的fail指针指到,这个点在trie中的深度就可以作为答案,当然,要维护最大值。
时间复杂度为O(n*常数)。
P.S. :这道题有一些细节问题容易忽略或打错。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<iostream>
#include<cstdlib>
#include<string>
#define maxn 100050
using namespace std;
struct smg
{
int fail,w[26],deep;
}a[maxn];
int cnt=0,n,i,j,now,que,wz[maxn],top1,top2,v,u,len1,len2,c;
int head,tail,x,y,ans,l,ii;
bool m1[maxn],m2[maxn];
string s[maxn],s1,s2;
inline void build(string s)
{
l=s.length(); now=0;
for (ii=0;ii<l;ii++)
{
if (a[now].w[s[ii]-'a']==0)
{
a[now].w[s[ii]-'a']=++cnt;
a[cnt].deep=ii+1;
}
now=a[now].w[s[ii]-'a'];
}
wz[i]=now;
}
void getfail()
{
queue<int> q;
for(int i=0;i<26;++i)
{
if(a[0].w[i]!=0)
{
a[a[0].w[i]].fail=0;
q.push(a[0].w[i]);
}
}
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=0;i<26;++i)
{
if(a[u].w[i]!=0)
{
a[a[u].w[i]].fail=a[a[u].fail].w[i];
q.push(a[u].w[i]);
}
else a[u].w[i]=a[a[u].fail].w[i];
}
}
}
int main()
{
scanf("%d",&n);
for (i=1;i<=n;i++)
{
cin>>s[i]; build(s[i]);
}
getfail();
scanf("%d",&que);
while (que--)
{
scanf("%d%d",&x,&y); ans=0;
memset(m1,0,sizeof(m1)); memset(m2,0,sizeof(m2));
s1=s[x]; s2=s[y]; len1=s1.length(); len2=s2.length();
u=0;
for (i=0;i<len1;i++)
{
c=s1[i]-'a'; u=a[u].w[c];
for (v=u;v&&(!m1[v]);v=a[v].fail) m1[v]=1;
}
u=0;
for (i=0;i<len2;i++)
{
c=s2[i]-'a'; u=a[u].w[c];
for (v=u;v&&(!m2[v]);v=a[v].fail) m2[v]=1;
}
for (i=1;i<=cnt;i++)
if (m1[i]&&m2[i])
if (a[i].deep>ans) ans=a[i].deep;
printf("%d\n",ans);
}
return 0;
}