[NOI2011]阿狸的打字机

Posted by Dispwnl on March 31, 2018

题目

题目背景

阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。

题目描述

打字机上只有28个按键,分别印有26个小写英文字母和’B’、’P’两个字母。经阿狸研究发现,这个打字机是这样工作的:

·输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。

·按一下印有’B’的按键,打字机凹槽中最后一个字母会消失。

·按一下印有’P’的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。

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

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

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

输入输出格式

输入格式:

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

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

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

输出格式:

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

输入输出样例

输入样例#1:

aPaPBbP
3
1 2
1 3
2 3

输出样例#1:

2
1
0

说明

数据范围:

对于100%的数据,n<=100000,m<=100000,第一行总长度<=100000。

题解

这题好妙啊…

首先这题$40$分暴力做法是$kmp$直接匹配

我们考虑同样作为字符串匹配算法的$AC$自动机

可以发现$fail$指针有一个神奇的性质:

如果一个字符串$a$的最后一位的$fail$指向字符串$b$的一个字符,那么$a$肯定包含$b$

所以原问题就可以转化为统计$x$最后一位在$fail$树里的子树里有多少$y$的字符

树结构+子树求和,想到了什么?

$dfs$序+线段树就可以搞了

所以按照原来的$Tire$树向下找,顺便在$dfs$序上标记

将询问离线,按$y$值排序,遍历字符串

然后对于每一个'P',因为这是打印,统计当前结尾对应的$y$所对应的询问,里面的$x$的子树和(好绕)

对于每一个'B',因为这是删除,所以讲当前节点的$dfs$序对应的值$-1$,并返回到ta的父节点

对于其他,对应的$dfs$序$+1$,然后就离线输出就行了

代码

# include<iostream>
# include<cstring>
# include<cstdio>
# include<queue>
# include<algorithm>
# define mid (l+r>>1)
# define tl (k<<1)
# define tr (k<<1|1)
using namespace std;
const int MAX=1e5+1;
struct p{
	int x,y;
}c[MAX];
struct q{
	int x,y,id;
	bool operator< (const q &a)
	const{
		return y<a.y;
	}
}qu[MAX];
int num,n,L,TOT,cnt,sum;
int h[MAX],ov[MAX],ans[MAX];
string a;
void add(int x,int y)
{
	c[++num]=(p){h[x],y},h[x]=num;
}
struct Tire{
	int fail[MAX],fa[MAX],die[MAX],id[MAX],siz[MAX];
	int vis[MAX][26],use[MAX][26];
	struct o{
		int x;
	}s[MAX<<2];
	void build()
	{
		int x=0;
		for(int i=0;i<L;++i)
		  {
		  	if(a[i]=='B')
		  	x=fa[x];
		  	else if(a[i]=='P')
		  	ov[++cnt]=x;
		  	else
		  	{
		  		if(!vis[x][a[i]-'a'])
				use[x][a[i]-'a']=vis[x][a[i]-'a']=++TOT,fa[TOT]=x;
				x=vis[x][a[i]-'a'];
			}
		  }
	}
	void GET_FAIL()
	{
		queue<int> qu;
		for(int i=0;i<26;i++)
		  {
		  	int v=vis[0][i];
		  	if(v) qu.push(v);
		  }
		while(!qu.empty())
		{
			int tt=qu.front();
			qu.pop();
			for(int i=0;i<26;i++)
			  {
			  	int v=vis[tt][i];
			  	if(v)
				{
					fail[v]=vis[fail[tt]][i];
					qu.push(v);
				} 
			  	else vis[tt][i]=vis[fail[tt]][i];
			  }
		}
	}
	void dfs(int x)
	{
		id[x]=++sum,siz[x]=1;
		for(int i=h[x];i;i=c[i].x)
		  {
		  	int y=c[i].y;
		  	if(y==x) continue;
		  	dfs(y);
		  	siz[x]+=siz[y];
		  }
	}
	void pus(int k)
	{
		s[k].x=s[tl].x+s[tr].x;
	}
	int ask(int l,int r,int k,int L,int R)
	{
		if(l==L&&r==R) return s[k].x;
		if(R<=mid) return ask(l,mid,tl,L,R);
		if(L>mid) return ask(mid+1,r,tr,L,R);
		return ask(l,mid,tl,L,mid)+ask(mid+1,r,tr,mid+1,R);
	}
	void change(int l,int r,int k,int x,int dis)
	{
		if(l==r)
		{
			s[k].x+=dis;
			return;
		}
		if(x<=mid) change(l,mid,tl,x,dis);
		else change(mid+1,r,tr,x,dis);
		pus(k);
	}
	void GET_ANS()
	{
		int tot=1,tot1=0;
		int x=0;
		for(int i=0;i<L;++i)
		  {
		  	if(a[i]=='P')
		  	{
		  		tot1++;
		  		while(qu[tot].y==tot1)
		  		{
		  			int xe=qu[tot].x;
		  			ans[qu[tot].id]=ask(1,sum,1,id[ov[xe]],id[ov[xe]]+siz[ov[xe]]-1);
		  			++tot;
				}
			}
			else if(a[i]=='B')
			change(1,sum,1,id[x],-1),x=fa[x];
			else
			x=use[x][a[i]-'a'],change(1,sum,1,id[x],1);
		  }
	}
}Tree;
int read()
{
	int x=0;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar());
	for(;isdigit(ch);x=x*10+ch-48,ch=getchar());
	return x;
}
int main()
{
	cin>>a;
	L=a.length();
	Tree.build();
	n=read();
	for(int i=1;i<=n;++i)
	  qu[i].x=read(),qu[i].y=read(),qu[i].id=i;
	sort(qu+1,qu+1+n);
	Tree.GET_FAIL();
	for(int i=0;i<=TOT;++i)
	  add(Tree.fail[i],i);
	Tree.dfs(0);
	Tree.GET_ANS();
	for(int i=1;i<=n;++i)
	  printf("%d\n",ans[i]);
	return 0;
}