[NOI2011]阿狸的打字机(AC自动机+树状数组)

【题解】

KMP算法:每次询问时求出x串的失配函数,然后在y串上匹配,总复杂度O(m*len) 可以得40分 

如果把所有单词建成一棵字母树,考虑类似的暴力:
对每次询问,枚举y串的每个点(将这个点理解为x在y串上的最后一个匹配点),若从它沿失配指针到root的路径经过x串的最后一个点,则答案加1
由于(x1,y),(x2,y),…这些y相同的询问在AC自动机上走的路径一样,可以对于y一次全部求得,将询问统计后离线处理即可 

做到这一步后,程序的时间主要浪费在哪里呢?发现对于每个y串,都要处理它的每个字符到root的路径,而其实许多串都有很长的公共前缀,这里重复计算了 
可以反过来考虑:每个x串的最后一个点,沿失配指针逆向走,寻找所有y上的点 
这时如果把x看成一棵树的树根,把失配指针看成树边,那问题就转化为求以x为根的子树中有多少点在y串上 
因此,以失配指针为树边,将AC自动机建成一棵树(称fail树),只需要想办法把y上的所有点标在树上,并可以实现快速查询就行了 
这样的话,若y2与y1有公共前缀,标记y2的点时公共前缀就不用重新操作了 

快速查询子树中有多少标记点,可以联想到区间快速求和(树状数组)
而子树的确可以转化为一段连续的区间:
dfs整棵fail树,对每个结点记录进入、最后离开时间(L,R),则满足L<=L1 因此y串每标记一个点t,就是将fail树对应数列的L[t]位+1,删除同理,询问时查询( L[x_last] , R[x_last] )的区间和就行了 


【代码】

#include
#include
#include
#include
using namespace std;
vector Ask[100005],Ans[100005],G[100005];//Ask[i]:所有y==i的询问对应的x 
char s[100005]={0};
int ch[100005][130]={0},ls[100005]={0},pre[100005]={0},L[100005]={0},R[100005]={0},c[200005]={0},f[100005]={0},q[100005]={0},x[100005]={0},y[100005]={0},p[100005]={0};
int len,u=0,sz=0,tot=0;
void tj(char t)
{
	if(ch[u][t]==0)
	{
		ch[u][t]=++sz;
		pre[ch[u][t]]=u;
	}
	u=ch[u][t];
}
void getf()
{
	int i,j,head=0,tail=0;
	for(i='a';i<='z';i++)
		if(ch[0][i]!=0)
		{
			q[tail++]=ch[0][i];
			G[0].push_back(ch[0][i]);
		}
	while(head0;p-=(p&(-p)))
		sum+=c[p];
	return sum;
}
int main()
{
	int m,i,j=0,k;
	scanf("%s",s);
	len=strlen(s);
	for(i=0;i='a'&&s[i]<='z') tj(s[i]);
		if(s[i]=='B') u=pre[u];
		if(s[i]=='P') ls[++j]=u;
	}
	scanf("%d",&m);
	for(i=1;i<=m;i++)
	{
		scanf("%d%d",&x[i],&y[i]);
		Ask[y[i]].push_back(x[i]);
	}
	getf();
	dfs(0);
	u=k=0;
	for(i=0;i='a'&&s[i]<='z')
		{
			u=ch[u][s[i]];
			jia(L[u],1);
		}
		if(s[i]=='B')
		{
			jia(L[u],-1);
			u=pre[u];
		}
		if(s[i]=='P')
		{
			k++;
			for(j=0;j


你可能感兴趣的:(字符串算法,树状数组&线段树)