AC自动机(fail数组指向反转建树)+树状数组+欧拉序

P2414 [NOI2011] 阿狸的打字机

Description

 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有28个按键,分别印有26个小写英文字母和'B'、'P'两个字母。
经阿狸研究发现,这个打字机是这样工作的:
l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
l 按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。
l 按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。
例如,阿狸输入aPaPBbP,纸上被打印的字符如下:
a
aa
ab
我们把纸上打印出来的字符串从1开始顺序编号,一直到n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中1≤x,y≤n),打字机会显示第x个打印的字符串在第y个打印的字符串中出现了多少次。
阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?

Input

 输入的第一行包含一个字符串,按阿狸的输入顺序给出所有阿狸输入的字符。
第二行包含一个整数m,表示询问个数。
接下来m行描述所有由小键盘输入的询问。其中第i行包含两个整数x, y,表示第i个询问为(x, y)。

Output

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

Sample Input

aPaPBbP 

1 2 
1 3 
2 3 

Sample Output



0

Hint

 1<=N<=10^5

1<=M<=10^5

输入总长<=10^5

Source
思路:题目来源:P2414 [NOI2011]

不能

这打字机比现在的还先进,哪里老式了

阿 机 的 打 字 狸

提前说明,前面的几个算法都是会TLE就不写注释了,最后AC的代码有详细注释

这是一个模式串互相匹配的题,而且询问由很多,会有多个x对应一个y,也肯一个x多个y,那么就是一个多模式匹配的问题,如果直接对每个询问找答案直接暴毙,根本不用想,

想一下根据输入先建立字典树,然后对trie建立fail数组,题目让找每个y中有几个x,那么就是在trie树上在y链扫的时候,每走到一个节点就一直跳fail,如果跳到了x的末尾那么就答案+1,思路是可以,但是时间复杂度爆炸,交OJ只有40分,我没写这个代码

然后优化一下,想一下,每个节点只有一个对应的fail树,那么如果把fail的指向反转一下,不就是一对多,不就是个树了,那么以这个fail树上的某个x的末节点为根的子树,该字数的所有节点就是可以通过原来的fail数组跳到x末节点的可以+1的节点,所以,我们只要在扫y链的时候每第一次扫到y链某点,就这个点标记一下,一直到y链末尾,再计算对于所有的该y链的询问,具体就是对询问<x,y>,遍历以x末节点为根的fail树,看有多少个节点被标记(此时只有y链上的点打标了),那么该询问的答案就是标记数。我们知道一棵树欧拉序的进序(DFS序),根和字数的序号是连着的,所以就是一个区间查询的问题,那么怎么查询呢?x先暴力吧感觉会爆炸

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
char text [maxn];
int word[maxn],alphabet,wordp,trie[maxn][26],triecnt,tag[maxn],fail[maxn],failcnt,queue[maxn],front,rear;
int words[maxn],wordsid[maxn],wordsidcnt=0,wordscnt,eulerstart[maxn],eulerend[maxn],eulercnt,idx[maxn],seg[maxn];
int ans[maxn];
struct egde1
{
	int tar,next,id;
}ques[maxn];
int queshead[maxn],quesegdecnt;
struct node2
{
	int to,next;
}reverseedge[maxn];
int reversehead[maxn],reversecnt;
int insert(int *w,int len)
{
	int u=0,c;
	for(int i=0;i<len;i++)
	{
		c=w[i];
		if(trie[u][c]==0)
		    trie[u][c]=++triecnt;
		u=trie[u][c];
	}
	tag[u]=1;
	return u;
}
void buildfail()
{
	int u=0;
	for(int i=0;i<26;i++)
	{
		if(trie[u][i])
		    queue[rear++]=trie[u][i];
	}
	while(front<rear)
	{
		u=queue[front++];
		for(int i=0;i<26;i++)
		{
			int j=fail[u];
			if(trie[u][i])
			{
				while(j!=0&&trie[j][i]==0)
	     		    j=fail[j];
		    	fail[trie[u][i]]=trie[j][i];
		    	queue[rear++]=trie[u][i];
			}
		}
	}
}
void buildreverse()
{
	for(int i=1;i<=triecnt;i++)
	{
		reverseedge[++reversecnt].next=reversehead[fail[i]];
		reversehead[fail[i]]=reversecnt;
		reverseedge[reversecnt].to=i;
	}
}
void buildeuler(int u)
{
	int v;
	eulerstart[u]=++eulercnt;
	idx[eulercnt]=u;
	for(v=reversehead[u];v;v=reverseedge[v].next)
		buildeuler(reverseedge[v].to);
	eulerend[u]=eulercnt;
}
void dfstrie(int u)
{
	seg[eulerstart[u]]++;
	if(tag[u])
	{
		for(int v=queshead[u];v;v=ques[v].next)
		{
			int tot=0;
			for(int i=eulerstart[ques[v].tar];i<=eulerend[ques[v].tar];i++)
			{
				tot+=seg[i];
			}
			ans[ques[v].id]=tot;
		}
	}
	for(int i=0;i<26;i++)
	{
		if(trie[u][i])
		{
			dfstrie(trie[u][i]);
		}
	}
	seg[eulerstart[u]]--;
}
int main()
{
	scanf("%s",text);
	int textlen=strlen(text);
	for(int i=0;i<textlen;i++)
	{
		alphabet=text[i]-'a';
		if(text[i]=='P')
		{
			wordsid[++wordsidcnt]=insert(word,wordp);
		}
		else if(text[i]=='B')
			wordp--;
		else
	    	word[wordp++]=alphabet;
	}
	buildfail();
	buildreverse();
	buildeuler(0);
	int q,x,y;
	scanf("%d",&q);
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d",&x,&y);
		ques[++quesegdecnt].next=queshead[wordsid[y]];
		ques[quesegdecnt].tar=wordsid[x];
		ques[quesegdecnt].id=i;
		queshead[wordsid[y]]=quesegdecnt;
	}

	dfstrie(0);
	for(int i=1;i<=q;i++)
	{
		printf("%d\n",ans[i]);
	}
    return 0;
}

然后交OJ一看,其实还好上面这个有70分,蛮不错,但是还有三个点TLE

那么再优化一下,可以用线段树来维护区间修改查询,好的上代码

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
char text [maxn];
int word[maxn],alphabet,wordp,trie[maxn][26],triecnt,tag[maxn],fail[maxn],failcnt,queue[maxn],front,rear;
int words[maxn],wordsid[maxn],wordsidcnt=0,wordscnt,eulerstart[maxn],eulerend[maxn],eulercnt,idx[maxn],seg[maxn];
int ans[maxn];
struct node
{
	int sum,l,r;
}segtree[4*maxn];
int segtreecnt;
struct egde1
{
	int tar,next,id;
}ques[maxn];
int queshead[maxn],quesegdecnt;
struct node2
{
	int to,next;
}reverseedge[maxn];
int reversehead[maxn],reversecnt;
int insert(int *w,int len)
{
	int u=0,c;
	for(int i=0;i<len;i++)
	{
		c=w[i];
		if(trie[u][c]==0)
		    trie[u][c]=++triecnt;
		u=trie[u][c];
	}
	tag[u]=1;
	return u;
}
void buildfail()
{
	int u=0;
	for(int i=0;i<26;i++)
	{
		if(trie[u][i])
		    queue[rear++]=trie[u][i];
	}
	while(front<rear)
	{
		u=queue[front++];
		for(int i=0;i<26;i++)
		{
			int j=fail[u];
			if(trie[u][i])
			{
				while(j!=0&&trie[j][i]==0)
	     		    j=fail[j];
		    	fail[trie[u][i]]=trie[j][i];
		    	queue[rear++]=trie[u][i];
			}
		}
	}
}
void buildreverse()
{
	for(int i=1;i<=triecnt;i++)
	{
		reverseedge[++reversecnt].next=reversehead[fail[i]];
		reversehead[fail[i]]=reversecnt;
		reverseedge[reversecnt].to=i;
	}
}
void buildeuler(int u)
{
	int v;
	eulerstart[u]=++eulercnt;
	idx[eulercnt]=u;
	for(v=reversehead[u];v;v=reverseedge[v].next)
		buildeuler(reverseedge[v].to);
	eulerend[u]=eulercnt;
}
void buildtree(int k,int l,int r)
{
	segtree[k].l=l;
	segtree[k].r=r;
	if(l==r)
	{
		return;
	}
	int mid=(l+r)/2;
	buildtree(2*k,l,mid);
	buildtree(2*k+1,mid+1,r);
	return;
}
void update(int k,int lo,int n)
{
	if(segtree[k].l==segtree[k].r)
	{
		segtree[k].sum+=n;
		return;
	}
	int mid=(segtree[k].l+segtree[k].r)/2; 
	if(lo<=mid)
	    update(2*k,lo,n);	
	else
		update(2*k+1,lo,n);	
	segtree[k].sum=segtree[2*k].sum+segtree[2*k+1].sum;
}
int query(int k,int l,int r)
{
	if(segtree[k].l==l&&segtree[k].r==r)
	{
		return segtree[k].sum;
	}
	int mid=(segtree[k].l+segtree[k].r)/2; 
	if(r<=mid)
	return query(2*k,l,r);
	else if(l>mid)
	return query(2*k+1,l,r);
	else
	return query(2*k,l,mid)+query(2*k+1,mid+1,r);
}
void dfstrie(int u)
{
    update(1,eulerstart[u],1);
	if(tag[u])
	{
		for(int v=queshead[u];v;v=ques[v].next)
		{
			ans[ques[v].id]=query(1,eulerstart[ques[v].tar],eulerend[ques[v].tar]);
		}
	}
	for(int i=0;i<26;i++)
	{
		if(trie[u][i])
		{
			dfstrie(trie[u][i]);
		}
	}
	update(1,eulerstart[u],-1);
}
int main()
{
	scanf("%s",text);
	int textlen=strlen(text);
	for(int i=0;i<textlen;i++)
	{
		alphabet=text[i]-'a';
		if(text[i]=='P')
		{
			wordsid[++wordsidcnt]=insert(word,wordp);
		}
		else if(text[i]=='B')
			wordp--;
		else
	    	word[wordp++]=alphabet;
	}
	buildfail();
	buildreverse();
	buildeuler(0);
	int q,x,y;
	scanf("%d",&q);
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d",&x,&y);
		ques[++quesegdecnt].next=queshead[wordsid[y]];
		ques[quesegdecnt].tar=wordsid[x];
		ques[quesegdecnt].id=i;
		queshead[wordsid[y]]=quesegdecnt;
	}
    buildtree(1,1,eulercnt);
	dfstrie(0);
	for(int i=1;i<=q;i++)
	{
		printf("%d\n",ans[i]);
	}
    return 0;
}

那么把这个交上去一看还是70,该不过的点还是没过。。。

再优化,线段树和树状数组都是logn级别的但是树状数组常数比线段树小得多,那就树状数组试一下

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
char text [maxn];
int word[maxn],alphabet,wordp,trie[maxn][26],triecnt,tag[maxn],fail[maxn],failcnt,queue[maxn],front,rear;
int words[maxn],wordsid[maxn],wordsidcnt=0,wordscnt,eulerstart[maxn],eulerend[maxn],eulercnt,idx[maxn],seg[maxn],treearray[maxn];
int ans[maxn];
struct egde1
{
	int tar,next,id;
}ques[maxn];
int queshead[maxn],quesegdecnt;
struct node2
{
	int to,next;
}reverseedge[maxn];
int reversehead[maxn],reversecnt;
int insert(int *w,int len)
{
	int u=0,c;
	for(int i=0;i<len;i++)
	{
		c=w[i];
		if(trie[u][c]==0)
		    trie[u][c]=++triecnt;
		u=trie[u][c];
	}
	tag[u]=1;
	return u;
}
void buildfail()
{
	int u=0;
	for(int i=0;i<26;i++)
	{
		if(trie[u][i])
		    queue[rear++]=trie[u][i];
	}
	while(front<rear)
	{
		u=queue[front++];
		for(int i=0;i<26;i++)
		{
			int j=fail[u];
			if(trie[u][i])
			{
				while(j!=0&&trie[j][i]==0)
	     		    j=fail[j];
		    	fail[trie[u][i]]=trie[j][i];
		    	queue[rear++]=trie[u][i];
			}
		}
	}
}
void buildreverse()
{
	for(int i=1;i<=triecnt;i++)
	{
		reverseedge[++reversecnt].next=reversehead[fail[i]];
		reversehead[fail[i]]=reversecnt;
		reverseedge[reversecnt].to=i;
	}
}
void buildeuler(int u)
{
	int v;
	eulerstart[u]=++eulercnt;
	idx[eulercnt]=u;
	for(v=reversehead[u];v;v=reverseedge[v].next)
		buildeuler(reverseedge[v].to);
	eulerend[u]=eulercnt;
}
void update(int k,int n)
{
	while(k<=eulercnt)
	{
		treearray[k]+=n;
		k+=(k&(-k));
	}
}
int sum(int k)
{
	int ans=0;
	while(k)
	{
		ans+=treearray[k];
		k-=(k&(-k));
	}
	return ans;
}
void dfstrie(int u)
{
	update(eulerstart[u],1);
	if(tag[u])
	{
		for(int v=queshead[u];v;v=ques[v].next)
		{
			ans[ques[v].id]=sum(eulerend[ques[v].tar])-sum(eulerstart[ques[v].tar]-1);
		}
	}
	for(int i=0;i<26;i++)
	{
		if(trie[u][i])
		{
			dfstrie(trie[u][i]);
		}
	}
	update(eulerstart[u],-1);
}
int main()
{
	scanf("%s",text);
	int textlen=strlen(text);
	for(int i=0;i<textlen;i++)
	{
		alphabet=text[i]-'a';
		if(text[i]=='P')
		{
			wordsid[++wordsidcnt]=insert(word,wordp);
		}
		else if(text[i]=='B')
			wordp--;
		else
	    	word[wordp++]=alphabet;
	}
	buildfail();
	buildreverse();
	buildeuler(0);
	int q,x,y;
	scanf("%d",&q);
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d",&x,&y);
		ques[++quesegdecnt].next=queshead[wordsid[y]];
		ques[quesegdecnt].tar=wordsid[x];
		ques[quesegdecnt].id=i;
		queshead[wordsid[y]]=quesegdecnt;
	}
	dfstrie(0);
	for(int i=1;i<=q;i++)
	{
		printf("%d\n",ans[i]);
	}
    return 0;
}

好的还是70真是棒呆了,那么到底卡在那了?其实是建立trie树的问题,会有很多大量重复字母的单词插入比如aaaaaaaaaaab和aaaaaaaaaaaaaaac这样建树的复杂度就暴毙了(其实这道题的描述里可以看出来会有大量这种数据),所以普通建树方法不能用了,那就直接在main里面用每次字母插入trie树,单词结束就打标,然后因为遇到'B'要删除一个字母,那么我们用个fa数组记录父亲方便回溯,至此AC代码就出来了,卡了我两天的题终于结束了

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
char text [maxn];//储存文本信息就是第一行的输入数据 
int trie[maxn][26],beifen[maxn][26],triecnt,tag[maxn],fa[maxn],wordsid[maxn],wordsidcnt=0;//这是关于建立字典树的储存,trie数组是原始的字典树,beifen数组是tire的备份,在求fail的时候beifen被修改了 
int fail[maxn],failcnt; 
int queue[maxn],front,rear;
int eulerstart[maxn],eulerend[maxn],eulercnt,idx[maxn];//这个是存欧拉序的,eulerstart其实也是dfs序,主要是区间查询要用线段树或者树状数组维护 
int seg[maxn],treearray[maxn];//seg是由欧拉序构成的区间,treearray是树状数组 
int ans[maxn];
struct node1
{
	int tar,next,id;
}ques[maxn];//问题是用图存的,具体就是链式前向星 
int queshead[maxn],quesegdecnt;
struct node2
{
	int to,next;
}reversenode[maxn];//这个是把faill数组的指向调头之后构成的faill树,这里命名为rewerse,因为和std库里重合了,就改成reversenode 
int reversehead[maxn],reversecnt;
void buildfail()//这里是建立fail树,由于建立的时候逐个跳faill很慢,要用路径压缩就要就该trie树,那么我用备份来完成路径压缩,保证原来trie树不变 
{//说一下路径压缩的时候,会对字典树修改,但是生产的faill数组与通过逐个跳faill指针算法的结果是一样的 
	int u=0; 
	for(int i=0;i<26;i++)
	    if(beifen[u][i])
		    queue[rear++]=beifen[u][i];
	while(front<rear)
	{
		u=queue[front++];
		for(int i=0;i<26;i++)
		{
			if(beifen[u][i])
			{
				fail[beifen[u][i]]=beifen[fail[u]][i];
				queue[rear++]=beifen[u][i];//路径压缩 
			}
			else
			    beifen[u][i]=beifen[fail[u]][i];//路径压缩 
		}
	}
}
void buildreverse()//把faill指针反转,变成faill树,存入reversenode 
{
	for(int i=1;i<=triecnt;i++)
	{
		reversenode[++reversecnt].next=reversehead[fail[i]];
		reversehead[fail[i]]=reversecnt;
		reversenode[reversecnt].to=i;
	}
}
void buildeuler(int u)//建立欧拉序,方便区间查询(从O(n)降到O(logn)),核心算法是DFS 
{
	int v;
	eulerstart[u]=++eulercnt;
	idx[eulercnt]=u;
	for(v=reversehead[u];v;v=reversenode[v].next)
		buildeuler(reversenode[v].to);
	eulerend[u]=eulercnt;
}
void update(int k,int n)//这里的区间查询和修改用的是后缀数组 
{
	while(k<=eulercnt)
	{
		treearray[k]+=n;
		k+=(k&(-k));//就是个lowbit 
	}
}
int sum(int k)//树状数组的性质导致区间只能查询1-k的和,就是前缀和 
{
	int ans=0;
	while(k)
	{
		ans+=treearray[k];
		k-=(k&(-k));
	}
	return ans;
}
void dfstrie(int u)
{
	update(eulerstart[u],1);
	if(tag[u])
	{
		for(int v=queshead[u];v;v=ques[v].next)
		{
			ans[ques[v].id]=sum(eulerend[ques[v].tar])-sum(eulerstart[ques[v].tar]-1);//由于sum查询的是前缀和,别忘了start的位置-1 
		}
	}
	for(int i=0;i<26;i++)
	{
		if(trie[u][i])
		{
			dfstrie(trie[u][i]);
		}
	}
	update(eulerstart[u],-1);
}
int main()
{
	scanf("%s",text);
	int textlen=strlen(text);
	int now=0;
	for(int i=0;i<textlen;i++)//这里不是整个单词的插入,是一个一个字母插入,而且不是每次从根节点开始,如果整个单词插入会TLE 
	{
		int c=text[i]-'a';
		if(text[i]=='P')
		{
			tag[now]=1;
			wordsid[++wordsidcnt]=now;
		}
		else if(text[i]=='B')
			now=fa[now];
		else
		{
			if(trie[now][c])
			{
				now=trie[now][c];
			}
			else
			{
				trie[now][c]=++triecnt;
				beifen[now][c]=triecnt;
		    	fa[triecnt]=now;
		    	now=triecnt;
			}
		}	
	}
	buildfail();
	buildreverse();
	buildeuler(0);
	int q,x,y;
	scanf("%d",&q);
	for(int i=1;i<=q;i++)//离线问题,用链式前向星存 
	{
		scanf("%d%d",&x,&y);
		ques[++quesegdecnt].next=queshead[wordsid[y]];
		ques[quesegdecnt].tar=wordsid[x];
		ques[quesegdecnt].id=i;
		queshead[wordsid[y]]=quesegdecnt;
	}
	dfstrie(0);
	for(int i=1;i<=q;i++)
	{
		printf("%d\n",ans[i]);
	}
    return 0;
}

2021.8.10

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值