BZOJ2434: [Noi2011]阿狸的打字机
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
题解Here!
这题有毒!这题有毒!!这题有毒!!!
先说一说正解:
AC自动机没得说。。。
将插入操作改成在线处理,不然你会TLE的。
然后,画出一个AC自动机的简图,你会发现一个有$(keng)$趣$(die)$的结论:
对于AC自动机中的每一个节点,如果节点A的$fail$指向节点B,那么B对应的字符串一定在A对应的字符串中出现!
于是,所有的询问变成了:
有多少个属于Y的节点的$fail$指针 _直接或间接_ 地指向X的结束位置。
然而还是会TLE。。。
再回看我们画的图,会发现:
所有的$fail$与原图中的节点构成了一颗树!
(我们一般称它为$fail$树。)
现在询问变成了:
在$fail$树中,以X结束点为根的子树中,有多少个点属于Y。
当然可以用树剖。。。
但是这里用树上差分。
每到一个点,我们就让它对应的序列点+1,回溯的时候-1。
这样做可以保证只有当前路径上的点是有值的。
如果当前点是一个结束节点,那么说明每一个属于这个串的节点都已统计完毕。
此时我们只需调出每一个关于这个串的询问,区间求和即可。
这玩意可以丢给树状数组,就不用第三遍dfs了。
处理询问的时候,离线将$y$从小到大排个序,求出左右端点,树状数组求个和即可。
然后就是乱七八槽的一大堆映射+变量名。。。
再讲一讲代码:
我调了一下午,然后发现一个巨大的问题:
AC自动机在建立$fail$指针的时候,会把$son$一并改掉。。。
然后你会发现你的遍历$Trie$树的$DFS$变成了死循环。。。
解决方案?$\tan(\frac{\pi}{2}+k\pi),k\in Z$
所以,再新建一个数组保存原来的$Trie$树吧。。。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #include<queue> #define MAXN 200010 using namespace std; int n=0,m,size=1,d=1,e=0; int ans[MAXN],head[MAXN],id[MAXN],low[MAXN],bst[MAXN]; int que_left[MAXN],que_right[MAXN],word[MAXN]; bool vis[MAXN]; struct AC{ int fail,val,fa,son[26],trie_son[26]; AC(){ fail=val=fa=0; memset(son,0,sizeof(son)); } }a[MAXN]; struct Fail_Tree{ int next,to; }b[MAXN<<1]; struct Question{ int x,y,id; }que[MAXN]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } inline bool cmp(const Question &p,const Question &q){ return p.y<q.y; } inline int idx(char c){return c-'a';} void buildtree(){ int u,v; queue<int> q; for(int i=0;i<26;i++) if(a[0].son[i]){ a[a[0].son[i]].fail=0; q.push(a[0].son[i]); } while(!q.empty()){ u=q.front(); q.pop(); for(int i=0;i<26;i++){ if(a[u].son[i]){ a[a[u].son[i]].fail=a[a[u].fail].son[i]; q.push(a[u].son[i]); } else a[u].son[i]=a[a[u].fail].son[i]; } } } inline int lowbit(int x){return x&(-x);} inline void update(int x,int v){ for(int i=x;i<=e;i+=lowbit(i))bst[i]+=v; } inline int sum(int x){ int s=0; for(int i=x;i;i-=lowbit(i))s+=bst[i]; return s; } inline void add(int x,int y){ b[d].to=y;b[d].next=head[x];head[x]=d++; } void dfs_fail(int rt){ id[rt]=++e; for(int i=head[rt];i;i=b[i].next){ int will=b[i].to; dfs_fail(will); } low[rt]=e; } void dfs_AC(int u){ update(id[u],1); if(a[u].val) for(int i=que_left[a[u].val];i<=que_right[a[u].val];i++) ans[que[i].id]=sum(low[word[que[i].x]])-sum(id[word[que[i].x]]-1); for(int i=0;i<26;i++)if(a[u].trie_son[i])dfs_AC(a[u].trie_son[i]); update(id[u],-1); } void work(){ m=read(); for(int i=1;i<=m;i++){ que[i].x=read();que[i].y=read(); que[i].id=i; } sort(que+1,que+m+1,cmp); for(int i=1;i<size;i++)add(a[i].fail,i); dfs_fail(0); for(int i=1,j=1;i<=m;i=j){ que_left[que[i].y]=i; while(que[j].y==que[i].y)j++; que_right[que[i].y]=j-1; } dfs_AC(0); for(int i=1;i<=m;i++)printf("%d\n",ans[i]); } void init(){ int len; char ch[MAXN>>1]; scanf("%s",ch); len=strlen(ch); for(int i=0,u=0;i<len;i++){ if(u==0&&ch[i]=='P')continue; int c=idx(ch[i]); switch(ch[i]){ case 'P':{word[++n]=u;a[u].val=n;break;} case 'B':{u=a[u].fa;break;} default:{ if(!a[u].son[c]){ a[u].son[c]=a[u].trie_son[c]=size++; a[a[u].son[c]].fa=u; } u=a[u].son[c]; break; } } } buildtree(); } int main(){ init(); work(); return 0; }