首页 > 代码库 > BZOJ 2434 NOI2011 阿狸的打字机 fail树+树状数组

BZOJ 2434 NOI2011 阿狸的打字机 fail树+树状数组

题目大意:初始字串为空,首先给定一系列操作序列,有三种操作:

1.在结尾加一个字符

2.在结尾删除一个字符

3.打印当前字串

然后多次询问第x个打印的字串在第y个打印的字串中出现了几次

卡了很久……到底还是对AC自动机了解不是很深啊QAQ

fail树不是很难想 至少在用AC自动机切掉3172之后不是很难想……

首先构建AC自动机 注意由于这个字串的特殊构造 我们不必每打印一个字符串再插入Trie树

令now为当前指针 初始为root 考虑三种操作

在结尾添加一个字符->新建一个子节点(若存在在不用新建),进入该子节点

在结尾删除一个字符->返回到父亲节点

打印当前字串->在当前节点标记是第几个字符串

那么查询x在y中出现了几次 就是查询y有多少个节点沿着fail指针能找到x (AC自动机基本操作)

那么我们反向思考 查询y有多少个节点沿着fail指针能找到x 就是查询x沿着反向的fail指针能找到多少个y的节点

fail指针没有环 每个节点只有一个出度 那么反向之后显然是一棵树 x沿着反向的fail指针所能到达的节点就是x所在的子树

于是我们可以沿着反向的fail指针搞出DFS序 x所在的子树就是DFS中对应的区间 我们要查询的是x对应的区间中有多少个y

想高级数据结构的可以洗洗睡了

我们可以这么搞

对于每个y:

我们把关于y的询问都存在邻接表里 然后把y所有的节点在DFS序中的位置插入树状数组,然后对于关于y的每个询问在树状数组上查询一遍即可。

那么问题来了:这不会TLE么?

答案是不会的 因为这题的特殊性 所以树状数组可以达到O(nlogn)

求出DFS序之后 我们回来考虑这些操作序列

在结尾添加一个字符->添加的字符所在节点加入树状数组

在结尾删除一个字符->删除的字符所在节点从树状数组删除

打印当前字串-> 处理询问

然后就搞出来了 1A我也是醉了

#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define M 100100
using namespace std;
struct Trie{
	Trie *son[26],*fail;
	vector<Trie*> next;
	int ed,pos;
	void* operator new (size_t);
}*root=new Trie,mempool[M],*C=mempool;
struct abcd{
	int to,pos,next;
}table[M];
int head[M],tot;
int n,m,ans[M];
char s[M];
int start[M],end[M],cnt;
int c[M];
void* Trie :: operator new (size_t)
{
	return C++;
}
void Add(int x,int y,int z)
{
	table[++tot].to=y;
	table[tot].pos=z;
	table[tot].next=head[x];
	head[x]=tot;
}
void Update(int x,int y)
{
	for(;x<=cnt;x+=x&-x)
		c[x]+=y;
}
int Get_Ans(int x)
{
	int re=0;
	for(;x;x-=x&-x)
		re+=c[x];
	return re;
}
void Build_Tree()
{
	int i;
	static Trie* stack[M];
	static int top;
	stack[++top]=root;
	for(i=1;s[i];i++)
	{
		switch(s[i])
		{
			case 'B':
				stack[top--]=0x0;
				break;
			case 'P':
				stack[top]->ed=++n;
				break;
			default:
				if(!stack[top]->son[s[i]-'a'])
					stack[top]->son[s[i]-'a']=new Trie;
				stack[++top]=stack[top-1]->son[s[i]-'a'];
				break;
		}
	}
	static Trie *q[M];
	static int r,h;
	for(i=0;i<26;i++)
		if(root->son[i])
		{
			root->son[i]->fail=root;
			root->next.push_back(q[++r]=root->son[i]);
		}
	while(r!=h)
	{
		Trie *p=q[++h];
		for(i=0;i<26;i++)
			if(p->son[i])
			{
				Trie *temp=p->fail;
				while(temp!=root&&!temp->son[i])
					temp=temp->fail;
				if(temp->son[i])
					temp=temp->son[i];
				p->son[i]->fail=temp;
				temp->next.push_back(q[++r]=p->son[i]);
			}
	}
}
void DFS(Trie *p)
{
	vector<Trie*>::iterator it;
	p->pos=++cnt;
	if(p->ed) start[p->ed]=cnt; 
	for(it=p->next.begin();it!=p->next.end();it++)
		DFS(*it);
	if(p->ed) end[p->ed]=cnt;
}
void Solve()
{
	int i,j;
	static Trie* stack[M];
	static int top;
	stack[++top]=root;
	for(j=1;s[j];j++)
	{
		switch(s[j])
		{
			case 'B':
				Update(stack[top]->pos,-1);
				stack[top--]=0x0;
				break;
			case 'P':
				for(i=head[stack[top]->ed];i;i=table[i].next)
					ans[table[i].pos]=Get_Ans(end[table[i].to])-Get_Ans(start[table[i].to]-1);
				break;
			default:
				stack[++top]=stack[top-1]->son[s[j]-'a'];
				Update(stack[top]->pos,1);
				break;
		}
	}
}
int main()
{
	int i,x,y;
	scanf("%s",s+1);
	Build_Tree();
	DFS(root);
	cin>>m;
	for(i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		Add(y,x,i);
	}
	Solve();
	for(i=1;i<=m;i++)
		printf("%d\n",ans[i]);
}


BZOJ 2434 NOI2011 阿狸的打字机 fail树+树状数组