【BZOJ-2434】阿狸的打字机 AC自动机 + Fail树 + DFS序 + 树状数组
2434: [Noi2011]阿狸的打字机
Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 2022 Solved: 1158
[Submit][Status][Discuss]
Description
阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有28个按键,分别印有26个小写英文字母和'B'、'P'两个字母。
经阿狸研究发现,这个打字机是这样工作的:
l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
l 按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。
l 按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。
例如,阿狸输入aPaPBbP,纸上被打印的字符如下:
a
aa
ab
我们把纸上打印出来的字符串从1开始顺序编号,一直到n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中1≤x,y≤n),打字机会显示第x个打印的字符串在第y个打印的字符串中出现了多少次。
阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?
Input
输入的第一行包含一个字符串,按阿狸的输入顺序给出所有阿狸输入的字符。
第二行包含一个整数m,表示询问个数。
接下来m行描述所有由小键盘输入的询问。其中第i行包含两个整数x, y,表示第i个询问为(x, y)。
Output
输出m行,其中第i行包含一个整数,表示第i个询问的答案。
Sample Input
3
1 2
1 3
2 3
Sample Output
1
0
HINT
1<=N<=10^5
Source
Solution
好题,值得多次练习
AC自动机无疑,建出Fail指针的同时,根据Fail指针反向建树,即Fail树
以x为根的子树沿着fail指针倒着走能找到多少个y路径上的点就说明x在y上出现过几次。
那么利用DFS,求出每个点的时间戳(即每个节点最先到达的时间和最后到达时间),那么发现,对于子树,是一种类似前缀和的存在
那么用线段树或者树状数组来维护一下
值得注意的几点:(皆为亲身经历)
首先在建Trie树的过程中,应该考虑直接对整个操作串进行处理,不断分离去插入会TLE
对于操作串的长度,不应该直接在for循环中使用strlen(),应该先记录下来len=strlen()后循环中直接用len,否则更TLE
不要企图在线处理,应该优先考虑离线处理,否则也会出现奇怪的错误
对于Fail树,应该建单向边,并非双向边,否则可能出现错误
数组要开的够大,题目中10^5,开到10^5+100,Tyvj秒A,CodeVS秒A,BZOJ狂WA不止,怒开2×10^5秒A...不知所措
Code
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> using namespace std; int read() { int x=0;char ch=getchar(); while(ch<'0'||ch>'9')ch=getchar(); while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x; } #define N 210000 int son[N][30],fail[N],q[N],sz,t,len,m,ans[N],fa[N],loc[N]; char s[N],opt[N]; struct data{int to,next;}edge[N],ask[N]; int cn,cnt,head[N],heada[N],lastq[N]; void addedge(int u,int v) {cnt++;edge[cnt].to=v;edge[cnt].next=head[u];head[u]=cnt;} void addask(int x,int y,int z) {ask[z].next=heada[y];heada[y]=z;ask[z].to=x;} int tree[N]; inline int lowbit(int x){return x&(-x);} void change(int loc,int val) { for (int i=loc; i<=t; i+=lowbit(i)) tree[i]+=val; } int query(int loc) { int ans=0; for (int i=loc; i>=1; i-=lowbit(i)) ans+=tree[i]; return ans; } int L[N],R[N]; void dfs(int x) { L[x]=++t; for (int i=head[x]; i; i=edge[i].next) dfs(edge[i].to); R[x]=++t; //printf("L=%d R=%d\n",L[x],R[x]); } void clear() {sz=1;for (int i=0; i<26; i++) son[0][i]=1;} void buildTrie() { int x=1,id=0; for (int i=0; i<len; i++) if (opt[i]=='P') loc[++id]=x; else if (opt[i]=='B') x=fa[x]; else { if (!son[x][opt[i]-'a']) son[x][opt[i]-'a']=++sz,fa[sz]=x; x=son[x][opt[i]-'a']; } } void buildFail() { int he=0,ta=1; q[0]=1; fail[1]=0; while (he<ta) { int now=q[he++]; for (int i=0; i<26; i++) { if (!son[now][i]) continue; int ff=fail[now]; while (!son[ff][i]) ff=fail[ff]; fail[son[now][i]]=son[ff][i]; q[ta++]=son[now][i]; } } for (int i=1; i<=sz; i++) addedge(fail[i],i); } void work() { int now=1,id=0; change(L[1],1); for (int i=0; i<len; i++) if (opt[i]=='P') { id++; for (int j=heada[id]; j; j=ask[j].next) ans[j]=query(R[loc[ask[j].to]])-query(L[loc[ask[j].to]]-1); } else if (opt[i]=='B') change(L[now],-1),now=fa[now]; else now=son[now][opt[i]-'a'],change(L[now],1); } //ACMachine int main() { clear(); scanf("%s",opt); len=strlen(opt); buildTrie(); buildFail(); m=read(); for (int x,y,i=1; i<=m; i++) x=read(),y=read(),addask(x,y,i); dfs(0); work(); for (int i=1; i<=m; i++) printf("%d\n",ans[i]); return 0; }
BZOJ数组大小被坑艰辛史