Sevenk Love Oimaster bzoj-2780 Spoj-8093

题目大意:给定$n$个大串和$m$次询问,每次给出一个字符串$s$询问在多少个大串中出现过。

注释:$1\le n\le 10^4$,$1\le q\le 6\cdot 10^4$,$the\ total\ length\ of\ n\ strings\ \le 10^5$,

$the\ total\ length\ of\ q\ question\ strings\le 3.6\times 10^5$。


想法:广义后缀自动机

先对$n$个串建立广义后缀自动机。

后缀自动机上每个节点记录下在多少个串串里出现过,记为$cnt_i$。

然后对于每个询问串,沿着$trans$指针走到当前的$now$节点,输出$cnt_{now}$即可。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <string>
#define N 200010 
using namespace std;
int n,m,last=1,tot=1,tr[N][26],fa[N],len[N],cnt[N],vis[N];
string s[N],ss;
void update(int c)
{
	int v=last,u=++tot;
	last=u;
	len[u]=len[v]+1;
	while(v&&!tr[v][c]) tr[v][c]=u,v=fa[v];
	if(!v) fa[u]=1;
	else
	{
		int x=tr[v][c];
		if(len[x]==len[v]+1) fa[u]=x;
		else
		{
			int y=++tot;
			memcpy(tr[y],tr[x],sizeof tr[y]);
			fa[y]=fa[x]; fa[x]=fa[u]=y;
			len[y]=len[v]+1;
			while(v&&tr[v][c]==x) tr[v][c]=y,v=fa[v];
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	int n,m;
	cin >> n >> m ;
	for(int i=1;i<=n;i++)
	{
		cin >> s[i] ;
		int l=s[i].length();
		last=1;
		for(int j=0;j<l;j++) update(s[i][j]-'a');
	}
	for(int i=1;i<=n;i++)
	{
		int l=s[i].length(),now=1;
		for(int j=0;j<l;j++)
		{
			now=tr[now][s[i][j]-'a'];
			int t=now;
			while(t&&vis[t]!=i)
			{
				cnt[t]++;
				vis[t]=i;
				t=fa[t];
			}
		}
	}
	for(int i=1;i<=m;i++)
	{
		cin >> ss ;
		int l=ss.length(),now=1;
		for(int j=0;j<l;j++) now=tr[now][ss[j]-'a'];
		printf("%d\n",cnt[now]);
	}
	return 0;
}

小结:对后缀自动机的理解好浅啊...