題意:
給出一個字符串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;
}