题意:
给出一个字符串s;
n次询问某个字符串xi的循环同构串在s中出现多少次;
|s|,∑|xi|<=10^6,n<=10^5;
题解:
WJMZBMR场的SAM题。。。
感觉还没学多久的后缀自动机姿势已经忘光了。。。悲伤哦;
首先考虑如何查询一个xi串在s中出现了多少次,这个只要直接用s的后缀自动机的trans指针匹配,然后得到的结点的right值就是答案了;
那么一个串xi的所有循环同构串就是将其倍长之后,里面长度为|xi|的子串们;
答案就是在后缀自动机上匹配,每次在后面加一个字符再在前面减一个字符,累加right值;
在前面减字符这个过程不太容易实现,所以转化一下,变成在需要的时候延pre指针向上走一次;
具体来讲这个正确的原因就是pre指针组成了反向后缀树,那么这个延反向后缀树向上相当于减去了一些前缀(减去长度未必为1,所以要在需要的时候减去);
注意累加答案的时候,经过了一次的结点不能再计算一遍,直接跳过即可,匹配的长度不够的结点也不应累加进去;
时间复杂度大概是线性的吧= =
代码:
#include<queue>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 2100000
#define S 26
using namespace std;
char s[N],str[N];
namespace SAM
{
int son[N<<1][S],pre[N<<1],len[N<<1],right[N<<1],in[N<<1];
int vis[N<<1];
int last,tot;
queue<int>q;
int newnode()
{
return ++tot;
}
void init()
{
tot=0;
last=newnode();
}
void Insert(int x)
{
int np=newnode(),p;
right[np]=1;
len[np]=len[last]+1;
for(p=last;p&&!son[p][x];p=pre[p])
son[p][x]=np;
if(!p)
pre[np]=1;
else
{
int q=son[p][x];
if(len[q]==len[p]+1)
pre[np]=q;
else
{
int nq=newnode();
len[nq]=len[p]+1;
pre[nq]=pre[q];
memcpy(son[nq],son[q],sizeof(int)*S);
pre[q]=pre[np]=nq;
for(;son[p][x]==q;p=pre[p])
son[p][x]=nq;
}
}
last=np;
}
void Build()
{
int x,i;
for(i=1;i<=tot;i++)
in[pre[i]]++;
for(i=1;i<=tot;i++)
if(!in[i])
q.push(i);
while(!q.empty())
{
x=q.front(),q.pop();
right[pre[x]]+=right[x];
in[pre[x]]--;
if(!in[pre[x]])
q.push(pre[x]);
}
}
}
int main()
{
int n,m,i,j,now,len,ans;
scanf("%s",s+1);
SAM::init();
m=strlen(s+1);
for(i=1;i<=m;i++)
SAM::Insert(s[i]-'a');
SAM::Build();
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%s",str+1);
m=strlen(str+1);
memcpy(str+m+1,str+1,sizeof(char)*m);
for(j=1,ans=0,now=1,len=0;j<m+m;j++)
{
while(now&&SAM::son[now][str[j]-'a']==0)
now=SAM::pre[now],len=min(len,SAM::len[now]);
if(!now) now=1,len=0;
else
now=SAM::son[now][str[j]-'a'],len=min(len,SAM::len[now])+1;
if(len>=m)
{
while(SAM::len[SAM::pre[now]]>=m)
now=SAM::pre[now],len=min(len,SAM::len[now]);
if(len>=m&&SAM::vis[now]!=i)
ans+=SAM::right[now],SAM::vis[now]=i;
}
}
printf("%d\n",ans);
}
return 0;
}