[NOI2011]阿狸的打字机(AC自动机)

题意

给定n个字符串,m次询问(\(n,m \leq 1e5\))每次询问一个串在另一串的出现次数,可以离线处理

思路

首先肯定是对n个串建AC自动机

如何求一个串的子串?假设i点对应这个字符串,那么从fail[ i ]就表示i串的最长后缀,一直向上跳fail,就可以得到i串的所有后缀。对于i的所有子串,我们可以理解为i的所有后缀的所有前缀,即每个后缀的前缀对应一个子串,他们一一对应
由于i在AC自动机上的父亲就是它的前缀,而一个点跳fail又可以跳出所有后缀,所以求i的子串等价于求i点到根节点的路径上所有点分别跳fail得到的结果

然而,一步步跳fail显然太暴力,对于aaaaaaa...这种数据很显然过不了,这个时候可以考虑fail树。由于j串对应的点只有一个,而如果i的一个前缀对应的节点p在j的子树中,那么p一定可以通过跳fail跳到j,此时ans应该加一

子树求和?

这时就可以想到离线做法了,dfs一遍原trie树,进入一个点将它在fail树上的权值加一,退出时减去,如果dfs到i点,那么就统计子树目标j在fail树上的子树和即为答案,子树求和怎么做?遍历fail树求一遍dfn序即可用树状数组维护

Code:

#include<bits/stdc++.h>
#define N 100005
#define lowbit(x) ((x)&(-x))
using namespace std;
int n,m,nxt[N][26],nnxt[N][26],fail[N],end[N],fa[N],hfu,ndsum;
int dfn[N],size[N],col;
int ans[N],c[N];
char a[N],b[N];int top;
struct Node {int son,i;};
struct Edge
{
	int next,to;
}edge[N<<1];int head[N],cnt;
vector <Node> nd[N];

void add_edge(int from,int to)
{
	edge[++cnt].next=head[from];
	edge[cnt].to=to;
	head[from]=cnt;
}
void read(int &x)
{
	char c;int sign=1;
	while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
	while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
}
void update(int x,int val) {for(;x<=col;x+=lowbit(x)) c[x]+=val;}
int query(int x) {int ret=0;for(;x;x-=lowbit(x)) ret+=c[x];return ret;}
int ask(int l,int r) {return query(r)-query(l-1);}
void get_fail()
{
	queue<int> q;
	for(int i=0;i<26;++i) if(nxt[0][i]) q.push(nxt[0][i]);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int i=0;i<26;++i)
		{
			if(nxt[u][i])
			{
				q.push(nxt[u][i]);
				fail[nxt[u][i]]=nxt[fail[u]][i];
			}
			else nxt[u][i]=nxt[fail[u]][i];
		}
	}
}
void work()
{
	int len=strlen(a),now=0;
	for(int i=0;i<len;++i)
	{
		if(a[i]!='B'&&a[i]!='P')
		{
			if(!nxt[now][a[i]-'a']) nxt[now][a[i]-'a']=++ndsum,fa[ndsum]=now;
			now=nxt[now][a[i]-'a'];
		}
		if(a[i]=='B') now=fa[now];
		if(a[i]=='P') end[++hfu]=now;
	}
}
void dfs(int rt)//fail dfn
{
	size[rt]=1;
	dfn[rt]=++col;
	for(int i=head[rt];i;i=edge[i].next) {dfs(edge[i].to);size[rt]+=size[edge[i].to];}
}
void dfs1(int rt)//solve
{
	update(dfn[rt],1);
	if(!nd[rt].empty())
	{
		for(int i=0;i<(int)nd[rt].size();++i)
		{
			int pt=dfn[nd[rt][i].son];
			ans[nd[rt][i].i]=ask(pt,pt+size[nd[rt][i].son]-1);
		}
	}
	for(int i=0;i<26;++i) if(nnxt[rt][i]) dfs1(nnxt[rt][i]);
	update(dfn[rt],-1);
}
int main()
{
	scanf("%s",a);
	work();
	for(int i=0;i<=ndsum;++i)
	for(int j=0;j<26;++j) nnxt[i][j]=nxt[i][j];
	get_fail();
	read(m);
	for(int i=1;i<=m;++i)
	{
		int x,y;
		read(x);read(y);
		nd[end[y]].push_back((Node){end[x],i});
	}
	for(int i=1;i<=ndsum;++i) add_edge(fail[i],i);
	dfs(0);
	dfs1(0);
	for(int i=1;i<=m;++i) printf("%d\n",ans[i]);
	return 0;
}
posted @ 2019-07-22 21:44  擅长平地摔的艾拉酱  阅读(133)  评论(0编辑  收藏  举报
/*取消选中*/