阿狸的打字机
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
aPaPBbP
3
1 2
1 3
2 3
Sample Output
2
1
0
HINT
1<=N<=10^5
1<=M<=10^5
输入总长<=10^5
1WA,原因:输出了打印次数个答案,而不是询问次数个答案,结果要么答案少一部分,要么多一堆迷之0……
还有这种操作……
(╯‵□′)╯︵┻━┻
思路:
首先,仔细观察题面,可以发现这个字符串在每个时间的状态都可以用一个栈表示。
那么:
插入一个字符等于在栈顶节点所在AC自动机上位置后插入一个儿子。
删除一个字符等于栈顶-1
打印所有字符等于将栈顶标记为一个字符串的结尾
然后咱就不需要把每个打印串分别求出并插入了~
然后,参见BZOJ3172,考虑使用fail树。
那么咱先对fail树求一遍dfs序,答案便是每个x节点的dfs区间中有多少个属于y的节点,这里属于y的节点的定义为,y串在AC自动机上的每个节点沿fail树走到根的路径上途径的所有点。
很显然,在dfs序上x节点的子树是一段连续的区间。
那么考虑再次模拟所有操作(其实就是对AC自动机进行dfs),使用一个树状数组维护。
每加入一个字符,就把在它dfs序上起点位置处给树状数组加上1。
每删除一个字符,就把刚才加的1减掉。
每遇到一次打印,那么便处理当前节点作为y时的所有询问:直接在树状数组上,对每个询问的x,查询其dfs序区间中的和为多少。
最后得到的查询结果即为在x子树中属于y节点的数量。
这就是答案了~
#include<iostream>
#include<vector>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int N=1e5+9;
int n,m,t,ans[N],dfn;
int bit[N],stk[N],top;
char operate[N];
int to[N],nxt[N],id[N],beg[N],tot;
inline void push(int u,int v,int w)
{
to[++tot]=v;
nxt[tot]=beg[u];
id[tot]=w;
beg[u]=tot;
}
inline int lowbit(int x)
{
return x&(-x);
}
inline void add(int x,int v)
{
while(x<=dfn)
{
bit[x]+=v;
x+=lowbit(x);
}
}
inline int query(int x)
{
int ret=0;
while(x)
{
ret+=bit[x];
x-=lowbit(x);
}
return ret;
}
struct AC_automaton
{
int ch[N][28],fail[N],end[N],pool;
int q[N],l,r,pos[N],st[N],ed[N];
vector<int> g[N];
inline int insert(int now,char c)
{
if(!ch[now][c-'a'])
ch[now][c-'a']=++pool;
return ch[now][c-'a'];
}
inline void set_end(int now,int id)
{
end[now]=id;
}
inline void getfail()
{
l=0;
q[r=1]=0;
fail[0]=0;
while(l<r)
{
int u=q[++l];
for(int i=0;i<26;i++)
if(ch[u][i])
{
q[++r]=ch[u][i];
fail[ch[u][i]]= u==0?0:ch[fail[u]][i];
g[fail[ch[u][i]]].push_back(ch[u][i]);
}
else
ch[u][i]= u==0?0:ch[fail[u]][i];
}
}
inline void dfs(int u)
{
pos[u]=++dfn;
if(end[u])
st[end[u]]=dfn;
for(int i=0,e=g[u].size();i<e;i++)
dfs(g[u][i]);
if(end[u])
ed[end[u]]=dfn;
}
}koishi;
int main()
{
scanf("%s",operate+1);
t=strlen(operate+1);
stk[top=1]=0;
for(int i=1;i<=t;i++)
switch(operate[i])
{
case 'B':
top--;
break;
case 'P':
koishi.set_end(stk[top],++n);
break;
default:
stk[++top]=koishi.insert(stk[top-1],operate[i]);
break;
}
koishi.getfail();
koishi.dfs(0);
scanf("%d",&m);
for(int i=1,x,y;i<=m;i++)
{
scanf("%d%d",&x,&y);
push(y,x,i);
}
stk[top=1]=0;
for(int i=1;i<=t;i++)
switch(operate[i])
{
case 'B':
add(koishi.pos[stk[top]],-1);
stk[top--]=0;
break;
case 'P':
for(int i=beg[koishi.end[stk[top]]];i;i=nxt[i])
ans[id[i]]=query(koishi.ed[to[i]])-query(koishi.st[to[i]]-1);
break;
default:
stk[++top]=koishi.ch[stk[top-1]][operate[i]-'a'];
add(koishi.pos[stk[top]],1);
break;
}
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}