[NOI2011]阿狸的打字机

[NOI2011]阿狸的打字机

这题可以大大加深我们对自动AC机的理解。

一上来:哇,这是什么神仙读入啊,如果按照它的要求一个一个把所有串建出来的话,肯定会MLE呀!

后来想一想,它这么读入,肯定构成一棵树,并且,它刚好是字典树!

这里是建树的方法。

void ins(){
	int x=1;
	for(int i=0;i<S;i++){
		if(s[i]=='P')t[x].fin.push_back(++n),pos[n]=x;
		else if(s[i]=='B'){
			if(x==1)continue;
			x=t[x].fa;
		}else{
			if(!t[x].ch[s[i]-'a'])t[x].ch[s[i]-'a']=++cnt,t[cnt].fa=x,t[x].org[s[i]-'a']=true;
			x=t[x].ch[s[i]-'a'];
		}
	}
}

注意到这里有一个将串映射到trie中节点的数组\(pos\),以后有用。

然后就不会了,看了题解。

我们必须明确一点,就是一个节点的所有祖先\(fail\)内,包含了它的全部出现过的前缀。

因此,对于询问\((x,y)\),我们找到\(x\)串的结尾节点,则该节点在\(fail\)树中的子树内,有多少个节点出现在了\(y\)串中,则询问\((x,y)\)的答案就是多少。

因为对于每一个这样的节点,它总可以通过多次跳\(fail\)跳到\(x\)串的结尾节点上。

但是,就算这样,我们也无法非常便捷地求答案。

考虑转换思路,以\(y\)串为枚举对象,查询有多少节点在\(x\)串的结尾节点的子树内。

查询子树内信息,我们有经典结构dfs序

我们可以将询问离线下来,并将询问存储在\(y\)节点的位置上。然后在trie树上爆搜(注意是trie树,不是trie图,在求\(fail\)时连上的新点不能进入,不然会挂),每到一个节点就统计(以当前节点到根所表示的字符串)作为\(y\)的答案。

在dfs序上单点修改,区间求和,我们有树状数组

关键代码(爆炸警告):

for(int j=0;j<v[t[x].fin[i]].size();j++)
	res[v[t[x].fin[i]][j].second]=
    	ask(t[pos[v[t[x].fin[i]][j].first]].dfn+t[pos[v[t[x].fin[i]][j].first]].sz-1)-
        ask(t[pos[v[t[x].fin[i]][j].first]].dfn-1);

\(v\)是一个\(vector\),存的是询问,其中\(first\)\(x\)串,\(second\)是序号。

\(res\)是统计答案的数组。

\(ask\)是树状数组的前缀和。

\(dfn\)是dfs序。

\(sz\)\(fail\)树中子树大小。

注意哪里是\(fail\)树,哪里是trie树,哪里是trie图!!

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,S,cnt=1,head[100100],lim,pos[100100],BIT[100100],res[100100];
char s[100100];
struct Edge{
	int to,next;
}edge[200100];
void ae(int u,int v){
	static int tot;
	edge[tot].next=head[u],edge[tot].to=v,head[u]=tot++;
}
void add(int x,int val){
	while(x<=lim)BIT[x]+=val,x+=x&-x;
}
int ask(int x){
	int sum=0;
	while(x)sum+=BIT[x],x-=x&-x;
	return sum;
}
struct AC_automaton{
	int ch[26],fa,fail,dfn,sz;
	bool org[26];
	vector<int>fin;
}t[100100];
void ins(){
	int x=1;
	for(int i=0;i<S;i++){
		if(s[i]=='P')t[x].fin.push_back(++n),pos[n]=x;
		else if(s[i]=='B'){
			if(x==1)continue;
			x=t[x].fa;
		}else{
			if(!t[x].ch[s[i]-'a'])t[x].ch[s[i]-'a']=++cnt,t[cnt].fa=x,t[x].org[s[i]-'a']=true;
			x=t[x].ch[s[i]-'a'];
		}
	}
}
vector<pair<int,int> >v[100100];
queue<int>q;
void build(){
	for(int i=0;i<26;i++){
		if(!t[1].ch[i])t[1].ch[i]=1;
		else t[t[1].ch[i]].fail=1,q.push(t[1].ch[i]),ae(1,t[1].ch[i]);
	}
	while(!q.empty()){
		int x=q.front();q.pop();
		for(int i=0;i<26;i++){
			if(t[x].ch[i])t[t[x].ch[i]].fail=t[t[x].fail].ch[i],q.push(t[x].ch[i]),ae(t[t[x].fail].ch[i],t[x].ch[i]);
			else t[x].ch[i]=t[t[x].fail].ch[i];
		}
	}
}
void getdfn(int x){
//	printf("%d\n",x);
	t[x].dfn=++lim,t[x].sz=1;
	for(int i=head[x];i!=-1;i=edge[i].next)getdfn(edge[i].to),t[x].sz+=t[edge[i].to].sz;
//	printf("%d\n",x);
}
void dfs(int x){
	add(t[x].dfn,1);
	for(int i=0;i<t[x].fin.size();i++){
//		printf("%d:%d\n",x,t[x].fin[i]);
		for(int j=0;j<v[t[x].fin[i]].size();j++)res[v[t[x].fin[i]][j].second]=ask(t[pos[v[t[x].fin[i]][j].first]].dfn+t[pos[v[t[x].fin[i]][j].first]].sz-1)-ask(t[pos[v[t[x].fin[i]][j].first]].dfn-1);
	}
	for(int i=0;i<26;i++)if(t[x].org[i])dfs(t[x].ch[i]);
	add(t[x].dfn,-1);
}
int main(){
	scanf("%s",s),S=strlen(s),memset(head,-1,sizeof(head)),ins(),build(),getdfn(1);
	scanf("%d",&m);
	for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),v[y].push_back(make_pair(x,i));
	dfs(1);
	for(int i=1;i<=m;i++)printf("%d\n",res[i]);
	return 0;
}
posted @ 2020-04-26 17:34  Troverld  阅读(111)  评论(0编辑  收藏  举报