Fail树就是AC自动机建出来的Fail指针构成的树。

【bzoj3172】【xsy1713】单词

题意

给定一些单词,求每个单词在所有单词里面的出现次数。

分析

构建Fail树,记录每个单词最后一个位置在Fail树上的位置。

每个单词访问的每个节点的\(has+1\)。

然后在Fail树上进行树形dp。

每次询问,求对应位置的dp值。

核心代码

  1. int n;
  2. char s[L];
  3. int rt,tot;
  4. int loc[N];
  5. struct Trie {
  6. int nx[A];
  7. LL has;
  8. int fail;
  9. }tr[S];
  10. int q[S];
  11. int qh,qt;
  12. void Ins(int frm,int &now,char *s,int cur,int len) {
  13. if (!now)
  14. now=++tot;
  15. tr[now].has++;
  16. if (cur==len+1) {
  17. loc[frm]=now;
  18. return;
  19. }
  20. int go=s[cur]-'a';
  21. Ins(frm,tr[now].nx[go],s,cur+1,len);
  22. }
  23. void Build(void) {
  24. rep(i,0,A-1)
  25. if (!tr[rt].nx[i])
  26. tr[rt].nx[i]=rt;
  27. else {
  28. tr[tr[rt].nx[i]].fail=rt;
  29. q[++qt]=tr[rt].nx[i];
  30. }
  31. while (qh!=qt) {
  32. int now=q[++qh];
  33. rep(i,0,A-1)
  34. if (!tr[now].nx[i])
  35. tr[now].nx[i]=tr[tr[now].fail].nx[i];
  36. else {
  37. tr[tr[now].nx[i]].fail=tr[tr[now].fail].nx[i];
  38. q[++qt]=tr[now].nx[i];
  39. }
  40. }
  41. per(i,qt,1)
  42. tr[tr[q[i]].fail].has+=tr[q[i]].has;
  43. }
  44. int main(void) {
  45. #ifndef ONLINE_JUDGE
  46. freopen("xsy1713.in","r",stdin);
  47. freopen("xsy1713.out","w",stdout);
  48. #endif
  49. rt=tot=1; scanf("%d",&n);
  50. rep(i,1,n) {
  51. scanf("%s",s+1);
  52. Ins(i,rt,s,1,strlen(s+1));
  53. }
  54. Build();
  55. rep(i,1,n) {
  56. LL t=tr[loc[i]].has;
  57. printf("%lld\n",t);
  58. }
  59. return 0;
  60. }

【bzoj2754】【xsy1712】喵星球上的点名

题意

知道\(N\)位学生的姓、名。

\(M\)次点名,每次若点到某位学生姓或名的子串,该学生要报告。

问每次有多少学生报告,且最后每位学生报告多少次。

分析1

把每次点名的信息,即多个字符串,建立AC自动机。

对于每一位学生,考虑它对答案的贡献。

一种暴力的想法是:

把学生的姓和名分别在AC自动机上跑一遍,跑到一个位置的时候沿着Fail指针往上跳暴力更新答案,用时间戳维护某位学生是否在某次中被点名。

由于随机生成的树的深度不会特别大。

所以这种做法的效率虽然是\(O(n^2)\),但是实际跑起来会很快。

这里有很多小小的细节。

由于这个字符没有什么限制,所以考虑使用map来连边,然后遍历的时候还要用map<int,int> iterator

由于一个终点位置可能有多个字符串,所以要使用vector来记录。

核心代码:

  1. //...
  2. #define rep(i,a,b) for (int i=(a);i<=(b);i++)
  3. #define pb push_back
  4. typedef map<int,int> MI;
  5. typedef map<int,int>::iterator MII;
  6. typedef vector<int> VI;
  7. //...
  8. int rd(void);
  9. int n,m;
  10. struct Name {
  11. int len; VI lis;
  12. void Read(void) {
  13. len=rd();
  14. rep(i,1,len) {
  15. int x=rd();
  16. lis.pb(x);
  17. }
  18. }
  19. }fam[N],uni[N],men[M];
  20. int q[S];
  21. int qh,qt;
  22. int rt,tot;
  23. struct T {
  24. int fail; MI nx;
  25. VI edp;
  26. }tr[S];
  27. int cnt[N];
  28. int vis[M],eff[M];
  29. //...
  30. void Init(void) {
  31. rt=tot=1;
  32. tr[rt].fail=rt;
  33. }
  34. void Ins(int frm,int &now,Name nam,int cur) {
  35. if (!now)
  36. now=++tot;
  37. if (cur==nam.len+1) {
  38. tr[now].edp.pb(frm);
  39. return;
  40. }
  41. int go=nam.lis[cur-1];
  42. Ins(frm,tr[now].nx[go],nam,cur+1);
  43. }
  44. void Build(void) {
  45. q[++qt]=rt;
  46. while (qh!=qt) {
  47. int now=q[++qh];
  48. for (MII it=tr[now].nx.begin();it!=tr[now].nx.end();it++) {
  49. int go=it->first,nxp=it->second; int t=tr[now].fail;
  50. while (t!=rt&&!tr[t].nx.count(go))
  51. t=tr[t].fail;
  52. if (tr[t].nx.count(go)&&now!=rt)
  53. t=tr[t].nx[go];
  54. tr[nxp].fail=t;
  55. q[++qt]=nxp;
  56. }
  57. }
  58. }
  59. int Trans(int now,int go) {
  60. int t=now;
  61. while (t!=rt&&!tr[t].nx.count(go))
  62. t=tr[t].fail;
  63. if (tr[t].nx.count(go))
  64. t=tr[t].nx[go];
  65. return t;
  66. }
  67. void Update(int frm,int now) {
  68. int t=now;
  69. while (t!=rt) {
  70. rep(i,1,tr[t].edp.size()) {
  71. int to=tr[t].edp[i-1];
  72. if (vis[to]!=frm) {
  73. vis[to]=frm;
  74. eff[to]++;
  75. cnt[frm]++;
  76. }
  77. }
  78. t=tr[t].fail;
  79. }
  80. }
  81. void Traver(int frm,Name nam) {
  82. int now=rt;
  83. rep(i,1,nam.len) {
  84. int go=nam.lis[i-1];
  85. now=Trans(now,go);
  86. Update(frm,now);
  87. }
  88. }
  89. int main(void) {
  90. //...
  91. n=rd(),m=rd();
  92. rep(i,1,n) {
  93. fam[i].Read();
  94. uni[i].Read();
  95. }
  96. rep(i,1,m)
  97. men[i].Read();
  98. Init();
  99. rep(i,1,m)
  100. Ins(i,rt,men[i],1);
  101. Build();
  102. rep(i,1,n) {
  103. Traver(i,fam[i]);
  104. Traver(i,uni[i]);
  105. }
  106. //...
  107. }

分析2

上述算法的问题的复杂度瓶颈出现在:

跑到一个位置的时候沿着Fail指针暴力更新答案

这一步随机下来虽然很快,但是它是\(O(n)\)的。

我们考虑对它进行优化。

对于每一个学生,它访问的节点,是Fail树上多条到根的路径的并。

考虑两个询问。

【询问2】

求经过一个点的路径的并的个数

我们只需要把路径的并上的每一个点的点权+1即可。

实现方法(粘自popoqqq的博客)

http://blog.csdn.net/PoPoQQQ/article/details/43020531

将所有节点按照DFS序排序

每个点到根的路径上的所有节点权值+1

相邻两个点的LCA到根的路径上的所有节点权值-1

即是树链的并

当然我们不必修改路径查询单点 只需对单点进行修改 然后查询子树 用树状数组维护DFS序即可

然而这道题并不用动态更改。

所以前缀标记一下就好了。

【询问1】求一个路径的并经过的点的点权之和

把每个作为字符串结尾的点的点权赋为1。

在树上预处理一下前缀和。

每次树上倍增,并用前缀和快速搞出来就好了。

分析3

前两种方法都是对点名的信息建立AC自动机。

但实际上,对姓、名建立AC自动机亦可以。

首先,根据题目的限制条件。

我们可以把姓和名用一个连字符连接起来,当做一个字符串来处理。

对姓名建立AC自动机,标记终点。

然后顺序处理每一个点名信息。

【询问1】首先把该点名信息在AC自动机上跑到终点。

若跑不到终点就跑不了了,那么答案就是0。

若跑到了终点,那么答案就是该点在Fail树上对应的子树中,有多少个点被标记。

这个东西提前进行树形dp即可。

【询问2】把点名信息放在Fail树上。

Fail树自顶向下遍历,统计每个点到根的路径中被标记的次数。

小结

(1)多个字符串的处理手段

对于多个字符串的匹配等问题,常考虑能否把多个字符串通过中间加连字符的方式合并为一个字符串,化简问题。

这种思考的东西通常是逐步满足的思考。

(2)树链的并

一些初步的认知:

求dfn序。相邻两个求LCA进行转化。

(3)树上的暴力算法

树上有种随机起来效率不错的暴力算法:树上每个点往根跳。

这很多时候可以骗分。

这基于随机情况下,树上的点的深度不会特别大。

(4)关于map

  1. typedef map<int,int> MI;
  2. typedef map<int,int>::iterator MII;
  3. MII mp;
  4. void Traverse(void) {
  5. for (MII it=mp.begin();it!=mp.end();it++) {
  6. int x=it->first,y=it->second;
  7. //...
  8. }
  9. }

【bzoj3881】【xsy1714】Divljak

题意

Alice有n个字符串S_1,S_2...S_n,Bob有一个字符串集合T,一开始集合是空的。

接下来会发生q个操作,操作有两种形式:

“1 P”,Bob往自己的集合里添加了一个字符串P。

“2 x”,Alice询问Bob,集合T中有多少个字符串包含串S_x。(我们称串A包含串B,当且仅当B是A的子串)

Bob遇到了困难,需要你的帮助。

分析

分析1

把\(S_1,S_2,...,S_n\)建立AC自动机。

那么,对于每个操作1,相当于对到根的树链的并上的每一个点增值;对于操作2,相当于求单点的值。

再次转化:单点修改,子树求和。

BIT+dfn序维护即可。

核心代码

  1. //...
  2. int n;
  3. char s[L];
  4. int rt,tot; int loc[N];
  5. struct T {
  6. int nx[A];
  7. int fail;
  8. }tr[S];
  9. int q[S]; int qh,qt;
  10. struct G {
  11. int v,nx;
  12. G(int _v=0,int _nx=0) {
  13. v=_v,nx=_nx;
  14. }
  15. }mp[E];
  16. int totG,hd[S];
  17. int dep[S]; int unit,pre[U][S];
  18. int siz[S]; int dfn[S],num;
  19. int m;
  20. char p[L];
  21. int lis[L];
  22. int trA[S];
  23. void InsTrie(int frm,char *s,int len) {
  24. int now=rt;
  25. rep(i,1,len) {
  26. int go=s[i]-'a';
  27. if (!tr[now].nx[go])
  28. tr[now].nx[go]=++tot;
  29. now=tr[now].nx[go];
  30. }
  31. loc[frm]=now;
  32. }
  33. void Build(void) {
  34. rep(i,0,A-1)
  35. if (!tr[rt].nx[i])
  36. tr[rt].nx[i]=rt;
  37. else {
  38. tr[tr[rt].nx[i]].fail=rt;
  39. q[++qt]=tr[rt].nx[i];
  40. }
  41. while (qh!=qt) {
  42. int now=q[++qh];
  43. rep(i,0,A-1)
  44. if (!tr[now].nx[i])
  45. tr[now].nx[i]=tr[tr[now].fail].nx[i];
  46. else {
  47. tr[tr[now].nx[i]].fail=tr[tr[now].fail].nx[i];
  48. q[++qt]=tr[now].nx[i];
  49. }
  50. }
  51. }
  52. void Ins(int u,int v) {
  53. mp[++totG]=G(v,hd[u]); hd[u]=totG;
  54. }
  55. void DFS(int now) {
  56. siz[now]=1; dfn[now]=++num;
  57. fore(k,now) {
  58. dep[mp[k].v]=dep[now]+1; pre[0][mp[k].v]=now;
  59. DFS(mp[k].v);
  60. siz[now]+=siz[mp[k].v];
  61. }
  62. }
  63. int lowbit(int i) {
  64. return i&-i;
  65. }
  66. void Add(int x,int ad) {
  67. for (int i=x;i<=tot;i+=lowbit(i))
  68. trA[i]+=ad;
  69. }
  70. int Query(int x) {
  71. int sum=0;
  72. for (int i=x;i>0;i-=lowbit(i))
  73. sum+=trA[i];
  74. return sum;
  75. }
  76. int LCA(int x,int y) {
  77. if (dep[x]<dep[y]) swap(x,y);
  78. per(i,unit,0)
  79. if (dep[x]-(1<<i)>=dep[y])
  80. x=pre[i][x];
  81. if (x==y) return x;
  82. per(i,unit,0)
  83. if (pre[i][x]!=pre[i][y])
  84. x=pre[i][x],y=pre[i][y];
  85. return pre[0][x];
  86. }
  87. void Traver(char *p,int len) {
  88. int now=rt;
  89. rep(i,1,len) {
  90. int go=p[i]-'a';
  91. now=tr[now].nx[go];
  92. lis[++lis[0]]=now;
  93. }
  94. }
  95. int cmp(int a,int b) {
  96. return dfn[a]<dfn[b];
  97. }
  98. void Modify(char *p,int len) {
  99. lis[0]=0;
  100. Traver(p,len);
  101. sort(lis+1,lis+lis[0]+1,cmp);
  102. // int t;
  103. lis[0]=unique(lis+1,lis+lis[0]+1)-lis-1;
  104. rep(i,1,lis[0]) {
  105. Add(dfn[lis[i]],1);
  106. // t=Query(dfn[2]+siz[2]-1)-Query(dfn[2]-1);
  107. }
  108. rep(i,1,lis[0]-1) {
  109. int anc=LCA(lis[i],lis[i+1]);
  110. Add(dfn[anc],-1);
  111. // t=Query(dfn[2]+siz[2]-1)-Query(dfn[2]-1);
  112. }
  113. }
  114. int QueryAns(int x) {
  115. int id=loc[x];
  116. int rl=Query(dfn[id]-1);
  117. int rr=Query(dfn[id]+siz[id]-1);
  118. int t=rr-rl;
  119. return t;
  120. }
  121. //...
  122. int main(void) {
  123. #ifndef ONLINE_JUDGE
  124. freopen("xsy1714.in","r",stdin);
  125. freopen("xsy1714.out","w",stdout);
  126. #endif
  127. n=rd(); rt=tot=1;
  128. rep(i,1,n) {
  129. scanf("%s",s+1);
  130. InsTrie(i,s,strlen(s+1));
  131. }
  132. Build();
  133. rep(i,2,tot)
  134. Ins(tr[i].fail,i);
  135. unit=(int)(log(tot)/log(2)); pre[0][rt]=rt;
  136. DFS(rt);
  137. rep(i,1,unit) rep(j,1,tot)
  138. pre[i][j]=pre[i-1][pre[i-1][j]];
  139. //...
  140. }

分析2

首先,我们考虑对P建立AC自动机。

离线建的功能肯定更强,所以我们离线所有操作,建立P的AC自动机。

考虑询问2,我们这样来描述:求AC自动机上某一个点在Fail树上的子树中被多少个不同的P访问过。所以我们只需要处理出每个P访问了那些点,然后把树的问题用dfn序转化,再用HH的项链那道题的方法解决即可。

【bzoj2434】【xsy1715】阿狸的打字机

题意

粘贴自popoqqq的博客:http://blog.csdn.net/PoPoQQQ/article/details/41518097

初始字串为空,首先给定一系列操作序列,有三种操作:

1.在结尾加一个字符

2.在结尾删除一个字符

3.打印当前字串

然后多次询问第x个打印的字串在第y个打印的字串中出现了几次

分析

首先在线建立Trie树。

处理出Fail指针。

一个问题可以描述为\((x,y)\):

求\(\sum_{在AC自动机上,y的祖先k}[在Fail树上,x为k的祖先]\)

我们可以在Fail上树上倍增,枚举\(y\)的祖先\(k\),判断\(x\)是否为\(k\)的祖先。

现在考虑优化。

注意这是多组询问,我们可以考虑把问题离线下来。

由于我们要枚举\(y\)的祖先,所以考虑把Trie树进行DFS,并用树状数组动态更新单点,查询就只是子树求和了。

用dfn序映射到一条直线上。

小结

Fail树是AC自动机建立之后的附属品,也是建立之后使用AC自动机的必需品。

使用Fail树关键要抓住这样一些性质:

【Fail树的性质】

对于Fail树上的两个节点\((i,j)\),若\(i\)为\(j\)的祖先,那么有:

①到达AC自动机的\(i\)节点对应的字符串 是 到达AC自动机的\(j\)节点对应的字符串 的 子串

②到达AC自动机的\(j\)节点对应的字符串 包含着 到达AC自动机的\(i\)节点对应的字符串

建立AC自动机的对象不同,就要使用不同的性质进行处理。

AC自动机 & Fail树 专题练习的更多相关文章

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

    2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 2545  Solved: 1419[Submit][Sta ...

  2. BZOJ 3172: [Tjoi2013]单词 [AC自动机 Fail树]

    3172: [Tjoi2013]单词 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 3198  Solved: 1532[Submit][Status ...

  3. 【BZOJ-3881】Divljak AC自动机fail树 + 树链剖分+ 树状数组 + DFS序

    3881: [Coci2015]Divljak Time Limit: 20 Sec  Memory Limit: 768 MBSubmit: 508  Solved: 158[Submit][Sta ...

  4. BZOJ2434 [Noi2011]阿狸的打字机(AC自动机 + fail树 + DFS序 + 线段树)

    题目这么说的: 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母.经阿狸研究发现,这个打字机是这样工作的: 输入小 ...

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

    2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 2022  Solved: 1158[Submit][Sta ...

  6. CF 163E. e-Government ac自动机+fail树+树状数组

    E. e-Government 题目: 给出n个字符串,表示n个人名,有两种操作: ?string ,统计字符串string中出现的属于城市居民的次数. +id,把编号为id的人变为城市居民,如果已经 ...

  7. BZOJ2905: 背单词 AC自动机+fail树+线段树

    $zjq$神犇一眼看出$AC$自动机 $Orz$ 直接就讲做法了 首先对每个串建出$AC$自动机 将$fail$树找到 然后求出$dfs$序 我们发现一个单词 $S_i$是$S_j$的子串当且仅当$S ...

  8. 【学习笔记】ac自动机&fail树

    定义 解决文本串和多个模式串匹配的问题: 本质是由多个模式串形成的一个字典树,由tie的意义知道:trie上的每一个节点都是一个模式串的前缀: 在trie上加入fail边,一个节点fail边指向这个节 ...

  9. BZOJ2434 [Noi2011]阿狸的打字机 【AC自动机 + fail树 + 树状数组】

    2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec  Memory Limit: 256 MB Submit: 3610  Solved: 1960 [Submit][S ...

随机推荐

  1. 源码安装python

    编译安装新版本python 一般来说python是linux系统的标配,但是版本一般却很老,而系统上面的很多服务可能与老的python存在依赖关系,我们又不能直接卸载.所以一般,我们可以在一个单独的目 ...

  2. shell 中的>文件重定向符 和 标准输入、输出、错误以及 2&1 的含义*

    http://www.cnblogs.com/chenmh/p/5382044.html 问:其中 的2>&1是怎么回事? . test.sh > test.log 2>&a ...

  3. 【Linux】linux常用基本命令

    Linux中许多常用命令是必须掌握的,这里将我学linux入门时学的一些常用的基本命令分享给大家一下,希望可以帮助你们.   这个是我将鸟哥书上的进行了一下整理的,希望不要涉及到版权问题. 1.显示日 ...

  4. 【转载】使用C#进行系统编程

    原文:使用C#进行系统编程 虽然对于系统编程(System programming)的定义很模糊,不过可以将其描述为在比特.字节.指令,或CPU周期层面所进行的思考.系统编程这个概念也暗含了对性能和可 ...

  5. HashCheck

    https://github.com/gurnec/HashCheck

  6. [SAP ABAP开发技术总结]ABAP程序之间数据共享与传递

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  7. 部署PDA程序的时候存储不足的解决办法

    通常Windows Mobile的存储空间分为程序内存和存储内存,默认都比较小,当程序比较大的时候可能无法正常部署到设备上面,针对此问题可采用如下办法解决:1.通过VS修改,工具--选项--设备--选 ...

  8. Codeforces Round #237 (Div. 2) B题模拟题

    链接:http://codeforces.com/contest/404/problem/B B. Marathon time limit per test 1 second memory limit ...

  9. 网上搜集的jq常用代码

    1. 设置IE特有的功能:  if ($.browser.msie) { //do something... } 2. 使用jQuery来代替一个元素: $('#thatdiv').replaceWi ...

  10. iOS - OC NSNull 空值

    前言 @interface NSNull : NSObject <NSCopying, NSSecureCoding> 作为占据空间的一个空值,如用在数组或字典中占据一个没有任何值的空间. ...