BZOJ_2434_[NOI2011]_阿狸的打字机_(AC自动机+dfs序+树状数组)
描述
http://www.lydsy.com/JudgeOnline/problem.php?id=2434
给出\(n\)个字符串,\(m\)个询问,对于第\(i\)个询问,求第\(x_i\)个字符串在第\(y_i\)个字符串中出现了多少次.
分析
首先我们可以想到对于串\(x\)和串\(y\),如果\(x\)在\(y\)中出现过,那么\(x\)一定是(\(y\)的某个前缀)的后缀,如果我们用所有字符串建立一个AC自动机,那么对于这个前缀,沿着失配边走,一定能走到\(x\)串.
所以我们得到了一个很直观但是复杂度很高的算法:对于每一个询问,枚举\(y\)中的字符作为前缀的结尾,沿着失配边走,看是否能够走到\(x\)串,复杂度是\(O(mL^2)\).显然这种算法是要超时的,我们需要考虑更优的算法.
这里先介绍fail树这一概念.对于AC自动机上的点\(x\),我们连一条\(f[x]\to{x}\)的有向边,这样就形成了一棵fail树.
这时我们会发现,之前说的能够沿着失配边找到\(x\)串的\(y\)串的后缀,一定在\(x\)的子树当中.我们用dfs序把树上问题转化为区间问题,问题就转化为了在区间内找符合条件的点有多少个.我们把\(y\)的后缀标为\(1\),其他全部标为\(0\),然后处理所有有关\(y\)的询问,由于是区间问题,可以用树状数组维护.
这样的算法的复杂度是\(O(mlogn)\)的.
1 #include <bits/stdc++.h> 2 using namespace std; 3 inline int read(int &x){x=0;int k=1;char c;for(c=getchar();c<'0'||c>'9';c=getchar())if(c=='-')k=-1;for(;c>='0'&&c<='9';c=getchar())x=x*10+c-'0';return x*=k;} 4 5 const int maxn=1e5+5,type=26; 6 int n,qct; 7 int ans[maxn]; 8 char s[maxn]; 9 struct query{ 10 int x[maxn],next[maxn],hd[maxn]; 11 inline void add_query(int a,int b){ 12 x[++qct]=a; next[qct]=hd[b]; hd[b]=qct; 13 } 14 }Q; 15 struct Aho_Corasick{ 16 int sz,cnt,ect; 17 int q[maxn],hd[maxn],f[maxn],fa[maxn],val[maxn],pos[maxn],l[maxn],r[maxn],c[maxn<<1]; 18 int ch[maxn][type]; 19 struct edge{ 20 int to,next; 21 edge(){} 22 edge(int to,int next):to(to),next(next){} 23 }g[maxn]; 24 Aho_Corasick(){sz=cnt=0;memset(ch[0],0,sizeof ch[0]);} 25 inline int id(char c){return c-'a';} 26 inline int lowbit(int x){return x&-x;} 27 inline void add_edge(int u,int v){g[++ect]=edge(v,hd[u]);hd[u]=ect;} 28 inline void build_trie(){ 29 int m=strlen(s+1); 30 int u=0; 31 for(int i=1;i<=m;i++){ 32 int c=id(s[i]); 33 if(c=='P'-'a'){val[u]=++cnt,pos[cnt]=u;continue;} 34 if(c=='B'-'a'){u=fa[u];continue;} 35 if(!ch[u][c]){ 36 memset(ch[++sz],0,sizeof ch[sz]); 37 ch[u][c]=sz; 38 fa[sz]=u; 39 } 40 u=ch[u][c]; 41 } 42 } 43 inline void get_fail(){ 44 int L=1,R=0; 45 for(int c=0;c<type;c++){ 46 int u=ch[0][c]; 47 if(u){f[u]=0;add_edge(0,u);q[++R]=u;} 48 } 49 while(L<=R){ 50 int u=q[L++]; 51 for(int c=0;c<type;c++){ 52 int t=ch[u][c]; 53 if(!t){ch[u][c]=ch[f[u]][c];continue;} 54 int v=f[u]; 55 f[t]=ch[v][c]; 56 add_edge(f[t],t); 57 q[++R]=t; 58 } 59 } 60 } 61 void dfs(int x,int &t){ 62 l[x]=++t; 63 for(int i=hd[x];i;i=g[i].next) dfs(g[i].to,t); 64 r[x]=++t; 65 } 66 inline void add(int x,int d){ 67 int R=sz<<1; 68 while(x<=R){ 69 c[x]+=d; 70 x+=lowbit(x); 71 } 72 } 73 inline int sum(int x){ 74 int ret=0; 75 while(x>0){ 76 ret+=c[x]; 77 x-=lowbit(x); 78 } 79 return ret; 80 } 81 inline void solve(){ 82 int m=strlen(s+1); 83 int u=0; 84 for(int i=1;i<=m;i++){ 85 int c=id(s[i]); 86 if(c=='B'-'a'){add(l[u],-1);u=fa[u];continue;} 87 if(c=='P'-'a'){ 88 int y=val[u]; 89 for(int j=Q.hd[y];j;j=Q.next[j]){ 90 int x=Q.x[j]; 91 ans[j]=sum(r[pos[x]])-sum(l[pos[x]]-1); 92 } 93 continue; 94 } 95 u=ch[u][c]; 96 add(l[u],1); 97 } 98 } 99 }ac; 100 inline void init(){ 101 scanf("%s",s+1); 102 ac.build_trie(); 103 read(n); 104 for(int i=1,a,b;i<=n;i++){ 105 read(a); read(b); 106 Q.add_query(a,b); 107 } 108 } 109 inline void solve(){ 110 ac.get_fail(); 111 int t=0; 112 ac.dfs(0,t); 113 ac.solve(); 114 for(int i=1;i<=n;i++) printf("%d\n",ans[i]); 115 } 116 int main(){ 117 init(); 118 solve(); 119 return 0; 120 }
2434: [Noi2011]阿狸的打字机
Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 2296 Solved: 1298
[Submit][Status][Discuss]
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
3
1 2
1 3
2 3
Sample Output
1
0
HINT
1<=N<=10^5
Source