bzoj 2434 阿狸的打字机
好久没写AC自动机了,学了SAM以后发现对AC自动机的理解也加深了,其实它们的精髓都是用后缀的前缀来处理字串(后缀数组也是)。
那么这个题光会写AC自动机可不行,还要会处理询问。这个题询问第x个串在第y个串中出现的次数,于是我们可以想到把所有串建AC自动机,然后利用fail指针的性质:沿着fail指针上溯,所经过的都是以当前节点为右端点的字串,也就是说fail[i]代表的字串在i中一定出现过。所以我们把fail边反向,出现了一颗fail树,那么如果一个串在P位置上出现了,那么在它的所有儿子中一定也出现了,于是可以用树状数组维护这棵fail树的dfs序来维护答案。
方法是:按照一开始建AC自动机的顺序遍历AC自动机,边走边在经过的位置上+1,如果删除的话就-1回溯。我们可以根据P的位置来确定当前状态是第几行(第几个串),如果和某些询问的y相同,然后我们对于这些询问的每一个x,找到它在dfs序中对应的子树的区间,计算这一段的和就行了。
思路还算比较清晰的,利用了fail指针的性质,方法比较巧妙。
typewriter
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cmath> 5 #include<cstring> 6 #define inf 2147483647 7 #define maxn 200000 8 using namespace std; 9 struct ask 10 { 11 int x,y,s; 12 }a[maxn]; 13 struct et 14 { 15 int s,t,next; 16 }e[maxn]; 17 int n,m,tot,num,now,rot,cnt,tim; 18 char s[maxn]; 19 int fir[maxn],fail[maxn],ch[maxn][27],fa[maxn],q[maxn]; 20 int t[maxn],ans[maxn],ll[maxn],rr[maxn],st[maxn]; 21 22 void insert(int w) 23 { 24 if (!ch[now][w]) ch[now][w]=++num; 25 fa[ch[now][w]]=now; 26 now=ch[now][w]; 27 } 28 29 void add(int x,int y) 30 { 31 e[++tot].s=x; e[tot].t=y; e[tot].next=fir[x]; fir[x]=tot; 32 } 33 34 void build() 35 { 36 int head=0,tail=1; 37 q[1]=rot; 38 while (head<tail) 39 { 40 int p=q[++head]; 41 for (int w=0;w<26;w++) 42 if (ch[p][w]) 43 { 44 int tmp=ch[p][w]; 45 int j=p; 46 if (j==rot) fail[tmp]=rot; 47 else 48 { 49 j=fail[j]; 50 while (j&&ch[j][w]==0) j=fail[j]; 51 if (ch[j][w]) fail[tmp]=ch[j][w]; 52 else fail[tmp]=rot; 53 } 54 add(fail[tmp],tmp); 55 q[++tail]=tmp; 56 } 57 } 58 } 59 60 void dfs(int i) 61 { 62 ll[i]=rr[i]=++cnt; 63 for (int j=fir[i];j;j=e[j].next) 64 { 65 int k=e[j].t; 66 if (fa[i]!=k) dfs(k); 67 rr[i]=cnt; 68 } 69 } 70 71 int getsum(int x) 72 { 73 int ans=0; 74 for (int i=x;i;i-=(i & -i)) ans+=t[i]; 75 return ans; 76 } 77 78 void ins(int x,int z) 79 { 80 for (int i=x;i<=cnt;i+=(i & -i)) t[i]+=z; 81 } 82 83 bool cmp(ask a,ask b) 84 { 85 return a.y<b.y; 86 } 87 88 int main() 89 { 90 //freopen("type.in","r",stdin); 91 scanf("%s",s); 92 int len=strlen(s); 93 rot=now=num=tim=0; 94 //建立AC自动机同时构造fail树 95 for (int i=0;i<len;i++) 96 { 97 if (s[i]=='B') now=fa[now]; 98 else 99 if (s[i]=='P') st[++tim]=now; 100 else 101 insert(s[i]-'a'); 102 } 103 build(); 104 cnt=0; 105 //遍历得到fail树的dfs序 106 dfs(rot); 107 //处理询问 108 scanf("%d",&m); 109 //按Y排序询问,以便把Y相同的一起处理 110 for (int i=1;i<=m;i++) 111 { 112 scanf("%d%d",&a[i].x,&a[i].y); 113 a[i].s=i; 114 } 115 sort(a+1,a+m+1,cmp); 116 //按照一开始建AC的顺序遍历AC自动机,如果遇到一个和询问的y[tim]相同的串,就查询相应的x 117 now=rot; 118 int tim=0,tail=1; 119 for (int i=0;i<len;i++) 120 { 121 if (s[i]=='B')//删除并回溯 122 { 123 ins(ll[now],-1); 124 now=fa[now]; 125 } 126 else 127 if (s[i]=='P')//区间查询 128 { 129 tim++; 130 while (a[tail].y==tim) 131 { 132 int tmp=st[a[tail].x]; 133 ans[a[tail].s]=getsum(rr[tmp])-getsum(ll[tmp]-1); 134 tail++; 135 } 136 } 137 else//单点插入 138 { 139 int w=s[i]-'a'; 140 now=ch[now][w]; 141 ins(ll[now],1); 142 } 143 } 144 for (int i=1;i<=m;i++) printf("%d\n",ans[i]); 145 return 0; 146 }
AC without art, no better than WA !