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;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章