bzoj-2780 Sevenk Love Oimaster

141 篇文章 0 订阅
75 篇文章 0 订阅

题意:

给出n个字符串和m个询问串;

求每个询问串是多少个字符串的子串;

n<=10000,m<=60000;

字符串总长度<=100000,询问串总长度<=360000;


题解:

传说中的多串匹配用的广义后缀自动机;

构建上的不同只在当trans(last,x)这个状态存在的时候,要进行一个讨论;

(当然,在单串的自动机中last不会有任何trans转移,所以这种情况只会在广义后缀自动机中出现;

具体就是如果last和trans(last,x)的len只差个1,那么trans(last,x)就是我们这次要插入的结点了,直接返回;

不然,就像下面的一样复制一个trans(last,x)结点搞一搞就可以了;

然后为了区分所有字符串 的后缀,我们在后缀结点上挂链标记就可以了;


对于一个输入的询问串,这个问题就等价于在广义后缀树上找到能代表这个串的结点,然后查询这个结点的子树内不同颜色数;

这个东西很像那个HH的项链嘛,搞出来个DFS序,然后预处理所有结点的答案即可;

因为这个问题最多有O(n)种询问,也就是说可以预处理出来之后再在线回答咯;


代码:


#include<map>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 110000
#define M 370000
#define K 70000
using namespace std;
char str[M];
int next[N<<1],to[N<<1],head[N<<1],pre[N],ce,tim;
int sum[N<<1],ans[N<<1];
namespace col
{
	int next[N<<1],val[N<<1],head[N<<1],ce;
	int add(int x,int y)
	{
		val[++ce]=y;
		next[ce]=head[x];
		head[x]=ce;
	}
}
int add(int x,int y)
{
	to[++ce]=y;
	next[ce]=head[x];
	head[x]=ce;
}
int lowbit(int x)
{
	return x&(-x);
}
void update(int x,int val)
{
	if(!x)	return ;
	while(x<N<<1)
	{
		sum[x]+=val;
		x+=lowbit(x);
	}
}
int query(int x)
{
	int ret=0;
	while(x)
	{
		ret+=sum[x];
		x-=lowbit(x);
	}
	return ret;
}
void dfs(int x)
{
	int t=++tim;
	for(int i=col::head[x];i;i=col::next[i])
	{
		update(pre[col::val[i]],-1);
		update(t,1);
		pre[col::val[i]]=t;
	}
	for(int i=head[x];i;i=next[i])
	{
		dfs(to[i]);
	}
	ans[x]=query(tim)-query(t-1);
}
namespace SAM
{
	int len[N<<1],pre[N<<1];
	map<char,int>son[N<<1];
	int tot,last;
	int newnode()
	{
		tot++;
//		len[tot]=pre[tot]=0;
//		son[tot].clear();
		return tot;
	}
	void init()
	{
		tot=0;
		last=newnode();
	}
	void Insert(char x,int i)
	{
		if(son[last][x])
		{
			int p=son[last][x];
			if(len[p]==len[last]+1)
				last=p;
			else
			{
				int np=newnode();
				len[np]=len[last]+1;
				pre[np]=pre[p];
				pre[p]=np;
				son[np]=son[p];
				for(int q=last;son[q][x]==p;q=pre[q])
					son[q][x]=np;
				last=np;
			}
		}
		else
		{
			int p,np=newnode();
			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];
					son[nq]=son[q];
					pre[q]=pre[np]=nq;
					for(;son[p][x]==q;p=pre[p])
						son[p][x]=nq;
				}
			}
			last=np;
		}
		col::add(last,i);
	}
	void Build()
	{
		for(int i=1;i<=tot;i++)
			add(pre[i],i);
	}
	int query(char *s)
	{
		int p=1;
		while(*s!='\0')
			p=son[p][*s],s++;
		return p;
	}
}
int main()
{
	int n,m,len,i,j,k;
	scanf("%d%d",&n,&m);
	SAM::init();
	for(i=1;i<=n;i++)
	{
		scanf("%s",str+1);
		len=strlen(str+1);
		SAM::last=1;
		for(j=1;j<=len;j++)
			SAM::Insert(str[j],i);
	}
	SAM::Build();
	dfs(1);
	for(i=1;i<=m;i++)
	{
		scanf("%s",str);
		printf("%d\n",ans[SAM::query(str)]);
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值