【CF235C】Cyclical Quest

题目

题目链接:https://codeforces.com/problemset/problem/235/C
给定一个主串 \(S\)\(n\) 个询问串,求每个询问串的所有循环同构在主串中出现的次数总和。
\(n\leq 10^5,|S|,\sum|T|\leq 10^6\)

思路

考虑字符串 \(abcde\)\(a\) 移动到最后一位时,实际上就是先将第一位删除,再添加一个新的字符。
而我们知道 SAM 的 parent 树的父子关系为在字符串前添加若干个字符,自动机上两个相邻的点等价于在字符串后面加上一个字符。所以我们考虑用 SAM 解决此题。
我们先枚举 \(T\) 的每一位,如果当前节点存在一个儿子为这个字符,那么就跳到这个字符;否则不断往 parent 树上的父亲跳。
同时维护一个变量 \(cnt\),表示当前字符串与自动机上的子串匹配了多少位。
然后再次枚举 \(T\),考虑把当前的字符移动到最后。如果此时 \(cnt\) 等于询问串的长度,那么显然删除后匹配的位数会减一,因为被我们删除的第一位本来是匹配的。
然后因为我们删除了一个字符,我们需要判断它的 \(\mathrm{endpos}\) 集合有没有改变(也就是匹配的长度是否等于了当前节点的父节点的长度),如果有,那么就往它的父节点跳。
接下来重复寻找下一个含有字符 \(c\) 的过程,然后继续维护好 \(cnt\) 即可。如果一次移动之后 \(cnt\) 等于询问串长度,那么就要加上位置不同的子串数量。这个很好预处理。
注意因为循环同构要求互不相同,所以我们要在 SAM 上遍历过的节点打上 tag。
时间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
using namespace std;

const int N=2000010;
int Q,n,m,vis[N],b[N],a[N];
char s[N],t[N];

struct SAM
{
	int last,tot,fa[N],ch[N][26],siz[N],len[N];
	SAM() { last=tot=1; }
	
	void ins(int c)
	{
		int p=last,np=++tot; 
		last=np; len[np]=len[p]+1; siz[np]=1;
		for (;p && !ch[p][c];p=fa[p]) ch[p][c]=np;
		if (!p) fa[np]=1;
		else
		{
			int q=ch[p][c];
			if (len[q]==len[p]+1) fa[np]=q;
			else
			{
				int nq=++tot;
				len[nq]=len[p]+1; fa[nq]=fa[q];
				for (int i=0;i<26;i++) ch[nq][i]=ch[q][i];
				fa[q]=fa[np]=nq;
				for (;p && ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
			}
		}
	}
	
	void topsort()
	{
		for (int i=1;i<=tot;i++) b[len[i]]++;
		for (int i=1;i<=tot;i++) b[i]+=b[i-1];
		for (int i=1;i<=tot;i++) a[b[len[i]]--]=i;
		for (int i=tot;i>=1;i--) siz[fa[a[i]]]+=siz[a[i]];
	}
	
	void solve(char *s)
	{
		m=strlen(s+1);
		int cnt=0,p=1,ans=0;
		for (int i=1;i<=m;i++)
		{
			int id=s[i]-'a';
			while (p && !ch[p][id])
				p=fa[p],cnt=len[p];
			if (!p) p=1,cnt=0;
			else cnt++,p=ch[p][id];
		}
		if (cnt==m)
		{
			cnt--;
			if (fa[p] && len[fa[p]]==cnt) p=fa[p];
		}
		for (int i=1;i<=m;i++)
		{
			int id=s[i]-'a';
			while (p && !ch[p][id])
				p=fa[p],cnt=len[p];
			if (!p) p=1,cnt=0;
			else
			{
				cnt++; p=ch[p][id];
				if (cnt==m)
				{
					if (vis[p]>Q) vis[p]=Q,ans+=siz[p];
					cnt--;
					if (fa[p] && len[fa[p]]==cnt) p=fa[p];
				}
			}
		}
		printf("%d\n",ans);
	}
}sam;

int main()
{
	scanf("%s%d",s+1,&Q);
	n=strlen(s+1);
	for (int i=1;i<=n;i++)
		sam.ins(s[i]-'a');
	sam.topsort();
	memset(vis,0x3f3f3f3f,sizeof(vis));
	while (Q--)
	{
		scanf("%s",t+1);
		sam.solve(t);
	}
	return 0;
}
posted @ 2021-01-09 13:09  stoorz  阅读(81)  评论(0编辑  收藏  举报