====== 引自洛谷 ======
题目描述:不描述了直接放个网页自己去看
阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有28个按键,分别印有26小写英文字母和B、P两个字母。经阿狸研究发现,这个打字机是这样工作的:
1 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
2 按一下印有 B 的按键,打字机凹槽中最后一个字母会消失。
3 按一下印有 P 的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。
例如,阿狸输入 ''aPaPBbP'',纸上被打印的字符如下:
''a
aa
ab''
我们把纸上打印出来的字符串从 1 开始顺序编号,一直到 n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数 (x,y)(其中 1≤x,y≤n,1≤x,y≤n),打字机会显示第 x 个打印的字符串在第 y 个打印的字符串中出现了多少次。
===== 涉及知识点 =====
ac自动机,dfs序,树状数组,树上差分
===== 分析 =====
简单来说,我们的目标来说是求,一个串中模式串的个数,很容易想到kmp和ac自动机,多个模式串批配,想到ac自动机。于是便想到模拟建立出ac自动机,然后再做考虑。ac自动机和kmp的区别无非是kmp链上建立fail边,而ac自动机是在trie树上建立fail边。其中ac自动机字符串匹配的方法就是跳fail边从一个起点不断往回跳记录个数。往回跳是啥?不就是树上动嘛,**我们考虑,将fail[j]=i,转化为一条有向边,由i指向j**,这样问题就很自然转化成了,询问fail树上,以x串结尾的节点为根的子树中有多少节点为y中的节点。于是这便是一道dfs序的题啦啦啦,具体来说,先dfs fail树,得到dfs序,保证任何一个根节点的子树在一段连续的序列上。连续序列单点修改维护区间,那必须是树状数组啊。建立一个BIT,维护dfs序的序列,然后对trie树进行dfs(注意如果是写拆trie的那种ac自动机的话要先复制一份trie),遇到一个节点,就将其对应dfs序+1,一个节点dfs完后再-1,保证始终只维护一个字符串的贡献。最后,离线处理查询,用类似链式前向星的方式查询一个字符串对另一个字符串的贡献。(记录每个查询对象为y的x字符串终止点,在dfs完一个字符串后统一查询和)
===== 几个坑点 =====
1 不接受任何暴力,一不小心就t qaq
2 trie树如果多加了一个0号节点,BIT维护的序列就要+1(被坑死了555)
3 这tm太难了,不愧是NOI,怎么想得到这么多技巧和数据结构……
===== 几个好处 =====
做完这题绝堆加深了对ac自动机以及dfs序,树上差分(勉强算吧)的理解,重新意识到nlogn数据结构的强大……
最后贴个代码
#include
using namespace std;
const int maxn=1e5+13;
char s[maxn];
int low[maxn];
int ch[maxn][26];
int CH[maxn][26];
int dfn[maxn];//dfs序
queue que;
int fail[maxn];
int a[maxn];//用于树状数组维护
int rt=0,now;
int he1[maxn],he2[maxn];
struct node{
int nx,v;
}tt[maxn];//fail存边
struct node1{
int nx,v;
int num;//询问存边
}q[maxn];
int cnt,cnt1,cnt2,cnt3,p;
int mp[maxn],rmp[maxn];
int fa[maxn],ans[maxn];
void add1(int u,int v)
{
tt[++cnt1]=(node){he1[u],v},he1[u]=cnt1;
}
void add2(int u,int v,int ci)
{
q[++cnt2]=(node1){he2[u],v,ci},he2[u]=cnt2;
}
int insert(char s)
{
if(s=='B') return fa[now];
if(s=='P')
{
mp[now]=++cnt;
rmp[cnt]=now;
return now;
}
int lo=s-'a';
if(!ch[now][lo])
{
CH[now][lo]=ch[now][lo]=++p;
fa[p]=now;
now=p;
}
else now=ch[now][lo];
return now;
}
void pre_ac()
{
int u=rt;
for(int i=0;i<=25;i++)
{
if(ch[u][i]) que.push(ch[u][i]);
}
while(!que.empty())
{
u=que.front();
que.pop();
for(int i=0;i<=25;i++)
{
if(ch[u][i])
{
fail[ch[u][i]]=ch[fail[u]][i],que.push(ch[u][i]);
}
else
{
ch[u][i]=ch[fail[u]][i];
}
}
}
}
struct BIT{
int l(int x)
{
return x&(-x);
}
void add(int x,int ad) {
for(;x<=p+1;x+=l(x)) a[x]+=ad;}//实际多算了一个节点……所以p+1
int query(int x) {
int ans=0;for(;x;x-=l(x)) ans+=a[x];return ans;}
}bt;
void dfs_1(int u)//对fail树的dfs
{
dfn[u]=++cnt3;
for(int i=he1[u];i;i=tt[i].nx)
{
int v=tt[i].v;
dfs_1(v);
}
low[u]=cnt3;
}
void dfs(int u)//对trie树的dfs
{
bt.add(dfn[u],1);
if(mp[u])
{
for(int i=he2[mp[u]];i;i=q[i].nx)
{
int v=q[i].v;
int nw=rmp[v];
ans[q[i].num]=bt.query(low[nw])-bt.query(dfn[nw]-1);
}
}
for(int i=0;i<=25;i++)
{
int y=CH[u][i];
if(y) dfs(y);
}
bt.add(dfn[u],-1);
}
int main()
{
int n,u,v;
scanf("%s",s);
for(int i=0;s[i];i++) now=insert(s[i]);
pre_ac();
for(int i=1;i<=p;i++)
{
add1(fail[i],i);
}
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&u,&v);
add2(v,u,i);
}
dfs_1(0);
dfs(0);
for(int i=1;i<=n;i++)
{
printf("%d\n",ans[i]);
}
}
这是洛谷上这题的链接[[https://www.luogu.com.cn/problem/P2414|阿狸的打字机]]