【bzoj2434】阿狸的打字机-AC自动机+fail树+优化

http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=23083

Description

阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有28个按键,分别印有26个小写英文字母和‘B‘、‘P‘两个字母。

经阿狸研究发现,这个打字机是这样工作的:

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

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

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

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

a

aa

ab

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

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

Sample Input

aPaPBbP31 21 32 3

Sample Output

210

题解:这道题做了好久。。哭泣。。。。。。。

主要是超时的问题。

fail树:按照AC自动机上fail的反向边建立的树。

如样例:

可以知道:failtree上,任意一点所代表的子串(即以trie上根到该点的串)是它在fail树上的子树任意一点的子串。

就是说,x是y的祖先,那么以y为结尾的串一直fail可以fail到x,就是x是y的子串。

那么现在可以得出一个简单粗暴的方法:

对于每个询问(x,y),求x在y上出现的次数,我们就可以在fail树上找到x串末尾的点A,然后询问y串在fail树上的每一个点是否是x的子树中的点,是就ans++。

但是明显,这个方法慢出天际了。。。。

优化:

(三颗星)离线。在fail树上用dfn给节点编号,则每一个点的子树在节点号上是连续的一段。在trie上走一遍,每到一个点x就在树状数组的dfn[x]位置+1,每离开一个点就在树状数组的dfn[x]位置-1。然后每当走到一个串的末尾,就询问每个对应要询问的x串,给fail树上它的子树求和,就是树状数组上getsum A~next[A]-1,这样就求出了y上多少个点为末尾可以fail到x,就是该询问(x,y)的答案。

后面就是我的错误点了:

(1)循环字符串的时候不要打for(int i=0;i<strlen(s);i++) ... 拿个变量存一下strlen(s)。。。超慢的。。

(2)做trietree的时候不用每次从头开始。。接着上一次的末尾就可以了。。快超多。。

(3)最后询问的时候不要把所有询问扫一遍。。用个first存y的边目录啊。。

血与泪啊。。

代码如下:

  1 #include<cstdio>
  2 #include<cstdlib>
  3 #include<cstring>
  4 #include<iostream>
  5 #include<vector>
  6 #include<queue>
  7 #include<cmath>
  8 using namespace std;
  9
 10 const int N=100010,S=26;
 11 struct node{
 12     int k,fa,fail,son[30];
 13 }a[N];
 14 struct edge{
 15     int x,y,next,fa,ans;
 16 }b[N],t[N];
 17 char s[N];
 18 int pre,m,tl,sl,num,cnt_s,len,cnt_dfn;
 19 int last[N],c[N],sk[N],dfn[N],next[N],first[N],fir[N];
 20 queue<int> q;
 21
 22 void clear(int x)
 23 {
 24     a[x].fa=a[x].k=a[x].fail=0;
 25     memset(a[x].son,0,sizeof(a[x].son));
 26 }
 27
 28 void buildAC()
 29 {
 30     while(!q.empty()) q.pop();
 31     for(int i=1;i<=S;i++)
 32         if(a[0].son[i]) q.push(a[0].son[i]);
 33     while(!q.empty())
 34     {
 35         int x=q.front();q.pop();
 36         int fail=a[x].fail;
 37         for(int i=1;i<=S;i++)
 38         {
 39             if(a[x].son[i])
 40             {
 41                 a[a[x].son[i]].fail=a[fail].son[i];
 42                 q.push(a[x].son[i]);
 43             }
 44             else a[x].son[i]=a[fail].son[i];
 45         }
 46     }
 47 }
 48
 49 void ins(int x,int y,bool bk)
 50 {
 51     if(!bk)
 52     {
 53         b[++len].x=x;b[len].y=y;
 54         b[len].next=first[x];first[x]=len;
 55     }
 56     else
 57     {
 58         t[++tl].x=x;t[tl].y=y;t[tl].ans=0;
 59         t[tl].next=fir[x];fir[x]=tl;
 60     }
 61 }
 62
 63 void dfs(int x)
 64 {
 65     dfn[x]=++cnt_dfn;
 66     for(int i=first[x];i;i=b[i].next)
 67     {
 68         int y=b[i].y;
 69         dfs(y);
 70     }
 71     next[x]=cnt_dfn;
 72 }
 73
 74 void build_fail_tree()
 75 {
 76     for(int i=1;i<=num;i++) ins(a[i].fail,i,0);
 77     dfs(0);
 78 }
 79
 80 int lowbit(int x){return x&(-x);}
 81 void add(int x,int d){for(int i=x;i<=N-10;i+=lowbit(i)) c[i]+=d;}
 82 int getsum(int x)
 83 {
 84     int ans=0;
 85     for(int i=x;i>=1;i-=lowbit(i)) ans+=c[i];
 86     return ans;
 87 }
 88
 89 int main()
 90 {
 91     freopen("a.in","r",stdin);
 92     freopen("me.out","w",stdout);
 93     num=0;cnt_s=0;cnt_dfn=0;len=0;pre=0;tl=0;clear(0);
 94     memset(fir,0,sizeof(fir));
 95     memset(first,0,sizeof(first));
 96     memset(c,0,sizeof(c));
 97     scanf("%s",s);
 98     //build trie tree
 99     int strl=strlen(s),now=0;
100     for(int i=0;i<strl;i++)
101     {
102         if(s[i]==‘P‘)
103         {
104             a[now].k=1;
105             last[++cnt_s]=now;
106         }
107         else if(s[i]==‘B‘) now=a[now].fa;
108         else
109         {
110             int ind=s[i]-‘a‘+1;
111             if(!a[now].son[ind])
112             {
113                 num++;clear(num);
114                 a[now].son[ind]=num;
115                 a[num].fa=now;
116             }
117             now=a[now].son[ind];
118         }
119     }
120     //build ac
121     buildAC();
122     //build fail tree
123     build_fail_tree();
124     scanf("%d",&m);
125     for(int i=1;i<=m;i++)
126     {
127         int x,y;
128         scanf("%d%d",&x,&y);
129         ins(y,x,1);
130     }
131     //find
132     now=0,cnt_s=0;
133     for(int i=0;i<strl;i++)
134     {
135         if(s[i]==‘B‘)
136         {
137             add(dfn[now],-1);
138             now=a[now].fa;
139         }
140         else if(s[i]==‘P‘)
141         {
142             cnt_s++;
143             for(int j=fir[cnt_s];j;j=t[j].next)
144             {
145                 int st=dfn[last[t[j].y]],ed=next[last[t[j].y]];
146                 t[j].ans=getsum(ed)-getsum(st-1);
147             }
148         }
149         else
150         {
151             int ind=s[i]-‘a‘+1;
152             now=a[now].son[ind];
153             add(dfn[now],1);
154         }
155     }
156     for(int i=1;i<=m;i++)
157         printf("%d\n",t[i].ans);
158     return 0;
159 }
时间: 2024-11-06 20:27:51

【bzoj2434】阿狸的打字机-AC自动机+fail树+优化的相关文章

【BZOJ-2434】阿狸的打字机 AC自动机 + Fail树 + DFS序 + 树状数组

2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 2022  Solved: 1158[Submit][Status][Discuss] Description 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母.经阿狸研究发现,这个打字机是这样工作的:l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最

[BZOJ2434]NOI2011阿狸的打字机|AC自动机|fail树|树状数组

这题真是太神了,好多实用的技巧..首先肯定是要先把每个要输出的串当模式串把自动机给建出来的,如果一个一个串复制出来再一个个插入显然非常慢...我们用在自动机上插入模式串的方法来建,初始时在0,新加一个字符就想下爬(或者新建),维护一个父亲指针,删除的时候就可以爬上去,这样就可以O(n)建出来了.. 再考虑询问的问题,每次把串拿出来再放进自动机跑一遍显然太慢..这里需要用到一个叫做fail树的东西,就是把fail指针当做边建成的一颗树..比如fail(i)=j,那么i在fail树上的父节点就是j.

BZOJ 2434: [Noi2011]阿狸的打字机 [AC自动机 Fail树 树状数组 DFS序]

2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 2545  Solved: 1419[Submit][Status][Discuss] Description 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母.经阿狸研究发现,这个打字机是这样工作的:l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最

[NOI2011][bzoj2434] 阿狸的打字机 [AC自动机+dfs序+fail树+树状数组]

题面 传送门 正文 最暴力的 最暴力的方法:把所有询问代表的字符串跑一遍kmp然后输出 稍微优化一下:把所有询问保存起来,把模板串相同的合并,求出next然后匹配 但是这两种方法本质没有区别,都是暴力 不那么暴力的 我们对于所有的串建立一个AC自动机,把询问按照$y$排序,然后在AC自动机上面跑,每次跳fail更新答案 这样可以拿到70分,但是时间上限还是会$O\left(n^2\right)$左右 巧妙的优化 这道题里面,所有的模板串和文本串都在AC自动机里 那么,题目中实际是在要求什么呢?

BZOJ 题目3172: [Tjoi2013]单词(AC自动机||AC自动机+fail树||后缀数组暴力||后缀数组+RMQ+二分等五种姿势水过)

3172: [Tjoi2013]单词 Time Limit: 10 Sec  Memory Limit: 512 MB Submit: 1890  Solved: 877 [Submit][Status][Discuss] Description 某人读论文,一篇论文是由许多单词组成.但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次. Input 第一个一个整数N,表示有多少个单词,接下来N行每行一个单词.每个单词由小写字母组成,N<=200,单词长度不超过10^6

【BZOJ2434】【NOI2011】阿狸的打字机 AC自动机

转载请注明出处233:http://blog.csdn.net/vmurder/article/details/42875307 这是一道神题. 首先我们需要建立AC自动机,然后再建个Fail树,之后发现 如果询问a串在b串中出现了几次,那么只需要看b串在AC自动机上所有的节点中有多少个节点,在a串的结束节点在Fail树上的子树中就可以了. 然后这样做就很可以了,但是仍然不能AC, 这时我们只需要按照Fail树的dfs序建立数据结构(我写了树状数组)进行区间查询就好了. 这时对于以上的b串,我们

BZOJ 2434 NOI 2011 阿狸的打字机 AC自动机构造fail树

题目大意:有一种打字机上有28个字母,分别是26个小写字母和BP,其中B代表退格,P代表换行,每一行就是一个字符串.现在给这些字符串标号,并询问x串在y串中出现过几次. 思路:这算是NOI史上最难的字符串的题了吧(动物园). 首先按照题意不难建一个AC自动机出来,按照正常的思路,对于每一个询问都需要在AC自动机上暴力的查找.但这样时间会十分好看. 于是我们想,fail指针构成的一定是一棵树,将这颗树的DFS序搞出来的话会有非常好的性质--串中存在某个字串的一定在这个字串的子树中,也就是对应DFS

BZOJ2434 NOI2011 阿狸的打字机 AC自动机+树状数组+DFS序

题意:给定三个操作:1.在当前字符串的末尾添加一个字符c  2.在当前字符串的末尾删除一个字符  3.记录当前字符串并对其标号.再给出N组询问,每组询问需回答第x个字符串在第y个字符串中出现的次数 题解: 首先按照如下规则建Trie,设当前节点为t,第i个字符串的结尾在Trie中的位置为mark[i]: 1.插入操作:看t是否有c这个儿子,有则t=t->child[c],否则t->child[c]=NewNode,t=t->child[c] 2.删除操作:t=t->father 3

BZOJ 2434: [Noi2011]阿狸的打字机( AC自动机 + DFS序 + 树状数组 )

一个串a在b中出现, 那么a是b的某些前缀的后缀, 所以搞出AC自动机, 按fail反向建树, 然后查询(x, y)就是y的子树中有多少是x的前缀. 离线, 对AC自动机DFS一遍, 用dfs序+树状数组维护, DFS到的查询点就回答询问.时间复杂度O(|ACAM|+QlogQ) ------------------------------------------------------------------------------------------- #include<cstdio>