P2414 [NOI2011] 阿狸的打字机

P2414 [NOI2011] 阿狸的打字机

题目描述:

[NOI2011] 阿狸的打字机

题目描述

阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有 28 个按键,分别印有 26 个小写英文字母和 BP 两个字母。经阿狸研究发现,这个打字机是这样工作的:

  • 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
  • 按一下印有 B 的按键,打字机凹槽中最后一个字母会消失。
  • 按一下印有 P 的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。

例如,阿狸输入 aPaPBbP,纸上被打印的字符如下:

a
aa
ab

我们把纸上打印出来的字符串从 1 开始顺序编号,一直到 n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数 (x,y)(其中 1x,yn),打字机会显示第 x 个打印的字符串在第 y 个打印的字符串中出现了多少次。

阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?

输入格式

输入的第一行包含一个字符串,按阿狸的输入顺序给出所有阿狸输入的字符。

第二行包含一个整数 m,表示询问个数。

接下来 m 行描述所有由小键盘输入的询问。其中第 i 行包含两个整数 x,y,表示第 i 个询问为 (x,y)

输出格式

输出 m 行,其中第 i 行包含一个整数,表示第 i 个询问的答案。

数据范围

对于 100% 的数据,1n1051m105,第一行总长度 105

-------------------------------------------------------------------------------------

根本不会字符串,所以我们先对询问重拳出击:

打字机会显示第 x个打印的字符串在第y个打印的字符串中出现了多少次

显然,可能有同一个y对应非常多个x,并且询问离线,所以我们可以 对每一次询问的y排序,将同一个y下所有的x一起统计答案

接下来我们考虑如何统计答案:

最暴力的办法当然是造一个AC自动机,对于每一个询问在AC自动机上跑一边.然而这样的时间复杂度并不优秀

根据大佬们的题解,AC自动机的 fail显然可以构成一颗树
我们考虑把这棵树建出来,(边的方向: t[x].fail->x)

建完树后我们会发现,之前从AC自动机的下往上跳,而现在是从上往下搜,这样的好处是:

你可以直接对所求y打上一个值为1标记,然后对于y下的每个x,从x开搜,搜索到的x子树下的权值即为询问(x,y)的答案

我们还能发现:

在一个子树内,dfn是连续的!
所以当我们想统计子树内的数据时,我们就可以用 线段树 或者 树状数组 这样 O(logn) 的数据结构来维护了

由于我又懒了我还想复习树状数组
所以这里选择用 树状数组 维护

Code

#include<bits/stdc++.h>
const int N=2e5+5;
using namespace std;
int dfn_cnt,n,m,e_cnt,tot;
int sum[N],head[N],dfn[N],low[N];
char c[N];
struct Edge{
int to,nxt;
}e[N<<1];
void add(int x,int y)
{
e[++e_cnt]={y,head[x]};
head[x]=e_cnt;
}
struct que{
int x,y,id,ans;
}q[N];
bool cmp1(que q1,que q2){return q1.y<q2.y;}
bool cmp2(que q1,que q2){return q1.id<q2.id;}
struct Trie{
int ch[26],ch_[26];
int fail,fa,id;
}t[N];
int lowbit(int x){return x&-x;}
void upd(int x,int val){while(x<=dfn_cnt)sum[x]+=val,x+=lowbit(x);}
int query(int x){int res=0;while(x)res+=sum[x],x-=lowbit(x);return res;}
int rt[N],ql[N],qr[N];
void get_fail()
{
queue<int> Q;
for(int i=0;i<26;i++)
{
if(t[0].ch[i])Q.push(t[0].ch[i]);
}
while(!Q.empty())
{
int u=Q.front();Q.pop();
for(int i=0;i<26;i++)
{
if(t[u].ch[i])
{
t[t[u].ch[i]].fail=t[t[u].fail].ch[i];
Q.push(t[u].ch[i]);
}
else t[u].ch[i]=t[t[u].fail].ch[i];
}
}
}
void get_dfn(int x)
{
dfn[x]=++dfn_cnt;
for(int i=head[x];i;i=e[i].nxt)
{
int to=e[i].to;
get_dfn(to);
}
low[x]=dfn_cnt;
}
void dfs(int x)
{
upd(dfn[x],1);
if(t[x].id)
{
for(int i=ql[t[x].id];i<=qr[t[x].id];i++)
{
q[i].ans=query(low[rt[q[i].x]])-query(dfn[rt[q[i].x]]-1);
}
}
for(int i=0;i<26;i++)
{
if(t[x].ch_[i])
{
dfs(t[x].ch_[i]);
}
}
upd(dfn[x],-1);
}
void work()
{
scanf("%s",c+1);
int len=strlen(c+1);int now=0;
//cout<<"len:"<<len<<"\n";
for(int i=1,cc;i<=len;i++)
{
if('a'<=c[i]&&c[i]<='z')
{
cc=c[i]-'a';
if(!t[now].ch[cc])t[now].ch[cc]=++tot,t[tot].fa=now;
now=t[now].ch[cc];
}
if(c[i]=='B')now=t[now].fa;
if(c[i]=='P')
{
rt[++n]=now;
t[now].id=n;
}
//cout<<now<<" ";
}
for(int i=0;i<=tot;i++)
{
for(int j=0;j<26;j++)
{
t[i].ch_[j]=t[i].ch[j];
}
}
cin>>m;
get_fail();
for(int i=1;i<=tot;i++)
{
add(t[i].fail,i);
}
get_dfn(0);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&q[i].x,&q[i].y);
q[i].id=i;
}
sort(q+1,q+1+m,cmp1);
for(int i=1,now=1;i<=m;i=now)
{
ql[q[i].y]=i;
while(q[i].y==q[now].y)now++;
qr[q[i].y]=now-1;
}
dfs(0);
sort(q+1,q+1+m,cmp2);
for(int i=1;i<=m;i++)
{
printf("%d\n",q[i].ans);
}
}
int main()
{
freopen("P2414.in","r",stdin);//freopen("P2414.out","w",stdout);
work();
}
posted @   liuboom  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示