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
aPaPBbP
3
1 2
1 3
2 3
Sample Output
2
1
0
HINT
1<=N<=10^5
1<=M<=10^5
输入总长<=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;
}
BZOJ2434: [Noi2011]阿狸的打字机
原文:https://www.cnblogs.com/Yangrui-Blog/p/9427391.html