Description
打字机上只有 \(28\) 个按键,分别印有 \(26\) 个小写英文字母和
B、P
两个字母,是这样工作的:
- 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
- 按一下印有
B
的按键,打字机凹槽中最后一个字母会消失。- 按一下印有
P
的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。例如,阿狸输入
aPaPBbP
,纸上被打印的字符如下:a aa ab
我们把纸上打印出来的字符串从 \(1\) 开始顺序编号,一直到 \(n\) 。
打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数 \((x,y)\)(其中 \(1\leqslant x,y\leqslant n\) ),打字机会显示第 \(x\) 个打印的字符串在第 \(y\) 个打印的字符串中出现了多少次。
比较有意思的 \(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。
#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;
}
BZOJ 2434: [Noi2011]阿狸的打字机 ACAM+fail树
原文:https://www.cnblogs.com/G-hsm/p/11443519.html