title
Description
打字机上只有 \(28\) 个按键,分别印有 \(26\) 个小写英文字母和
B、P
两个字母,是这样工作的:
- 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
- 按一下印有
B
的按键,打字机凹槽中最后一个字母会消失。- 按一下印有
P
的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。例如,阿狸输入
aPaPBbP
,纸上被打印的字符如下:a aa ab我们把纸上打印出来的字符串从 \(1\) 开始顺序编号,一直到 \(n\) 。
打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数 \((x,y)\)(其中 \(1\leqslant x,y\leqslant n\) ),打字机会显示第 \(x\) 个打印的字符串在第 \(y\) 个打印的字符串中出现了多少次。
analysis
比较有意思的 \(ACAM\) 了,不再是套板子的题目。
和一般的 \(ACAM\) 相比,因为多了两个键,所以在建树的时候,就要额外两个操作了:
P
:说明已经组成了一个单词,所以记录一下这个单词的结束位置;B
:就往当前节点的父亲走,因为把这个节点删了。
下面就是如何判断单词 \(x\) 是否是 \(y\) 的子串?
说两条性质:
\(Trie\) 树上一个节点的祖先节点所代表的单词,肯定是当前所代表的单词的前缀;
一个节点的 \(fail\) 指针指向的肯定是当前节点所代表的单词的最长后缀。
然后子串又可以被理解成 前缀的后缀 。
暴力:从根到 \(y\) 遍历,对于每个节点,向上暴跳 \(fail\) 指针,遇到单词 \(x\) 就 \(+1\) ;
正解:建立一颗 \(fail\) 树,那么问题就从考虑所有属于 \(y\) 的节点有哪些能够暴跳 \(fail\) 指针到单词 \(x\) 结尾,变成了考虑 \(fail\) 树中 \(y\) 的节点有哪些在 \(x\) 的子树中了。
对于询问呢?在线好像不是很好搞,那就离线。
按照建立 \(Trie\) 的过程遍历一遍 \(Trie\) 。
每向下走一步,就给刚到的节点的权值 \(+1\) ,每往上跳一步,就给离开的节点的权值 \(-1\) 。
如果跳到了第 \(i\) 个单词,就处理询问 \(y=i\) 时的所有情况,等同于查询一下 \(fail\) 树中 \(x\) 的子树权值和。
OK。
code
#include<bits/stdc++.h> const int maxn=1e5+10; namespace IO { char buf[1<<15],*fs,*ft; inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; } template<typename T>inline void read(T &x) { x=0; T f=1, ch=getchar(); while (!isdigit(ch) && ch^'-') ch=getchar(); if (ch=='-') f=-1, ch=getchar(); while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar(); x*=f; } char Out[1<<24],*fe=Out; inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; } template<typename T>inline void write(T x,char str) { if (!x) *fe++=48; if (x<0) *fe++='-', x=-x; T num=0, ch[20]; while (x) ch[++num]=x%10+48, x/=10; while (num) *fe++=ch[num--]; *fe++=str; } } using IO::read; using IO::write; struct Graph { int ver[maxn],Next[maxn],head[maxn],len; inline void Clear() { memset(head,0,sizeof(head)); len=0; } inline void add(int x,int y) { ver[++len]=y,Next[len]=head[x],head[x]=len; } }G, A; namespace BIT { int c[maxn<<1]; inline int lowbit(int x) { return x & -x; } inline void add(int x,int k) { while (x<=maxn) c[x]+=k, x+=lowbit(x); } inline int ask(int x) { int ans=0; while (x) ans+=c[x], x-=lowbit(x); return ans; } inline int hsh(int l,int r) { return ask(r)-ask(l-1); } } using BIT::add; using BIT::hsh; int l[maxn],r[maxn],Time,ans[maxn]; struct Aho_Corasick_AutoMaton { int son[maxn][26],fail[maxn],cnt; int pos[maxn],fa[maxn]; inline void insert(char *s) { int len=strlen(s+1), now=0, id=0; for (int i=1; i<=len; ++i) if (s[i]=='P') pos[++id]=now; else if (s[i]=='B') now=fa[now]; else { int c=s[i]-'a'; if (!son[now][c]) son[now][c]=++cnt, fa[cnt]=now; now=son[now][c]; } } inline void build() { std::queue<int>q; for (int i=0; i<26; ++i) if (son[0][i]) q.push(son[0][i]); while (!q.empty()) { int x=q.front(); q.pop(); for (int i=0; i<26; ++i) if (son[x][i]) fail[son[x][i]]=son[fail[x]][i], q.push(son[x][i]); else son[x][i]=son[fail[x]][i]; } } inline void solve(char *s) { int len=strlen(s+1), now=0, id=0; add(l[0],1); for (int i=1; i<=len; ++i) if (s[i]=='P') { ++id; for (int i=A.head[id]; i; i=A.Next[i]) { int y=pos[A.ver[i]]; ans[i]=hsh(l[y],r[y]); } } else if (s[i]=='B') add(l[now],-1), now=fa[now]; else now=son[now][s[i]-'a'], add(l[now],1); } } ACAM; inline void dfs(int x) { l[x]=++Time; for (int i=G.head[x]; i; i=G.Next[i]) { int y=G.ver[i]; dfs(y); } r[x]=Time; } char ch[maxn]; int main() { scanf("%s",ch+1); ACAM.insert(ch); ACAM.build(); for (int i=1; i<=ACAM.cnt; ++i) G.add(ACAM.fail[i],i); int m;read(m); for (int i=1,x,y; i<=m; ++i) read(x),read(y),A.Next[i]=A.head[y], A.head[y]=i, A.ver[i]=x; dfs(0); ACAM.solve(ch); for (int i=1; i<=m; ++i) write(ans[i],'\n'); IO::flush(); return 0; }