CF235C Cyclical Quest

题意:

给出一个字符串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;
}



你可能感兴趣的:(codeforces,后缀自动机,sam)