【bzoj2434-阿狸的打字机】AC自动机+fail树+优化

时间:2022-10-13 08:15:52

http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=23083

Description

阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有28个按键,分别印有26个小写英文字母和'B'、'P'两个字母。

经阿狸研究发现,这个打字机是这样工作的:

l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。

l 按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。

l 按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。

例如,阿狸输入aPaPBbP,纸上被打印的字符如下:

a

aa

ab

我们把纸上打印出来的字符串从1开始顺序编号,一直到n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中1≤x,y≤n),打字机会显示第x个打印的字符串在第y个打印的字符串中出现了多少次。

阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?

Sample Input

aPaPBbP
3
1 2
1 3
2 3

Sample Output

2
1
0

题解:这道题做了好久。。哭泣。。。。。。。

主要是超时的问题。

fail树:按照AC自动机上fail的反向边建立的树。

如样例:

【bzoj2434-阿狸的打字机】AC自动机+fail树+优化

可以知道:failtree上,任意一点所代表的子串(即以trie上根到该点的串)是它在fail树上的子树任意一点的子串。

就是说,x是y的祖先,那么以y为结尾的串一直fail可以fail到x,就是x是y的子串。

那么现在可以得出一个简单粗暴的方法:

对于每个询问(x,y),求x在y上出现的次数,我们就可以在fail树上找到x串末尾的点A,然后询问y串在fail树上的每一个点是否是x的子树中的点,是就ans++。

但是明显,这个方法慢出天际了。。。。

优化:

(三颗星)离线。在fail树上用dfn给节点编号,则每一个点的子树在节点号上是连续的一段。在trie上走一遍,每到一个点x就在树状数组的dfn[x]位置+1,每离开一个点就在树状数组的dfn[x]位置-1。然后每当走到一个串的末尾,就询问每个对应要询问的x串,给fail树上它的子树求和,就是树状数组上getsum A~next[A]-1,这样就求出了y上多少个点为末尾可以fail到x,就是该询问(x,y)的答案。

后面就是我的错误点了:

(1)循环字符串的时候不要打for(int i=0;i<strlen(s);i++) ... 拿个变量存一下strlen(s)。。。超慢的。。

(2)做trietree的时候不用每次从头开始。。接着上一次的末尾就可以了。。快超多。。

(3)最后询问的时候不要把所有询问扫一遍。。用个first存y的边目录啊。。

血与泪啊。。

代码如下:

 #include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<vector>
#include<queue>
#include<cmath>
using namespace std; const int N=,S=;
struct node{
int k,fa,fail,son[];
}a[N];
struct edge{
int x,y,next,fa,ans;
}b[N],t[N];
char s[N];
int pre,m,tl,sl,num,cnt_s,len,cnt_dfn;
int last[N],c[N],sk[N],dfn[N],next[N],first[N],fir[N];
queue<int> q; void clear(int x)
{
a[x].fa=a[x].k=a[x].fail=;
memset(a[x].son,,sizeof(a[x].son));
} void buildAC()
{
while(!q.empty()) q.pop();
for(int i=;i<=S;i++)
if(a[].son[i]) q.push(a[].son[i]);
while(!q.empty())
{
int x=q.front();q.pop();
int fail=a[x].fail;
for(int i=;i<=S;i++)
{
if(a[x].son[i])
{
a[a[x].son[i]].fail=a[fail].son[i];
q.push(a[x].son[i]);
}
else a[x].son[i]=a[fail].son[i];
}
}
} void ins(int x,int y,bool bk)
{
if(!bk)
{
b[++len].x=x;b[len].y=y;
b[len].next=first[x];first[x]=len;
}
else
{
t[++tl].x=x;t[tl].y=y;t[tl].ans=;
t[tl].next=fir[x];fir[x]=tl;
}
} void dfs(int x)
{
dfn[x]=++cnt_dfn;
for(int i=first[x];i;i=b[i].next)
{
int y=b[i].y;
dfs(y);
}
next[x]=cnt_dfn;
} void build_fail_tree()
{
for(int i=;i<=num;i++) ins(a[i].fail,i,);
dfs();
} int lowbit(int x){return x&(-x);}
void add(int x,int d){for(int i=x;i<=N-;i+=lowbit(i)) c[i]+=d;}
int getsum(int x)
{
int ans=;
for(int i=x;i>=;i-=lowbit(i)) ans+=c[i];
return ans;
} int main()
{
freopen("a.in","r",stdin);
freopen("me.out","w",stdout);
num=;cnt_s=;cnt_dfn=;len=;pre=;tl=;clear();
memset(fir,,sizeof(fir));
memset(first,,sizeof(first));
memset(c,,sizeof(c));
scanf("%s",s);
//build trie tree
int strl=strlen(s),now=;
for(int i=;i<strl;i++)
{
if(s[i]=='P')
{
a[now].k=;
last[++cnt_s]=now;
}
else if(s[i]=='B') now=a[now].fa;
else
{
int ind=s[i]-'a'+;
if(!a[now].son[ind])
{
num++;clear(num);
a[now].son[ind]=num;
a[num].fa=now;
}
now=a[now].son[ind];
}
}
//build ac
buildAC();
//build fail tree
build_fail_tree();
scanf("%d",&m);
for(int i=;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
ins(y,x,);
}
//find
now=,cnt_s=;
for(int i=;i<strl;i++)
{
if(s[i]=='B')
{
add(dfn[now],-);
now=a[now].fa;
}
else if(s[i]=='P')
{
cnt_s++;
for(int j=fir[cnt_s];j;j=t[j].next)
{
int st=dfn[last[t[j].y]],ed=next[last[t[j].y]];
t[j].ans=getsum(ed)-getsum(st-);
}
}
else
{
int ind=s[i]-'a'+;
now=a[now].son[ind];
add(dfn[now],);
}
}
for(int i=;i<=m;i++)
printf("%d\n",t[i].ans);
return ;
}