后缀自动机的一点理解
每个点代表的都是字符串里的一个子串
可以理解为对于字符串每个字串建的\(trie\)图
\(fail/link\)指向\(parent\)这个状态的最长后缀状态
\(\therefore parent.longest+1=now.shorest\)
\(endpos\):一个子串出现在原串的位置(末端点)集合
\(endpos\)集合相同的点在一个点表示串
建\(parents\)树,沿着树往下就是\(endpos\)
经常我们会按\(parents\)树递归建线段树维护\(endpos\)来处理一些问题
状态
endpos相同的子串为一个点,互为后缀,长度连续且不同
建自动机
就说说最难理解的部分吧,分点转移
\(np\)是新节点,\(p\)是\(lst\)某个有\(c\)的最长后缀,则\(q=trans(p,c)\)
\(q.len!=p.len+1\),也就是\(q.len>p.len+1\)
那这样\(np.fail=q\)的话,\(q.longest\)莫名其妙会成为\(np\)的后缀,但\(longest\)根本就不是从\(p\)转移过来的
所以我们单独拉出一个点\(nq\)维护\(p.longest+1\)就好了
经典匹配
在双字符串匹配中,对\(S\)建\(SAM\),\(T\)在上面跑的时候,从\(i(now)\)到\(i+1\),\(T\)已匹配长度为\(cnt\)
\(now\)是否有\(T[i+1]\)这个儿子
有:\(++cnt\)
没有:跑\(fail\),\(cnt=len[now]\)(捡长的后缀留下来),如果还没有继续跳;否则跳过去,\(++cnt\)
时间复杂度:从\(now\)跳\(fail\),但这次跑的花费会不会非常大,实际复杂度是有保证的,\(dep[now.fail]<dep[now]\),则相当于最多跳的次数为\(|T|\)
特殊匹配
多次查询\((l,r)\in S\)和\(T\)匹配的时候怎么办呢?
题目
题目1:给出字符串\(S\),求其中每个子串出现的次数*长度的最大值
分析:区分AC自动机,AC自动机将要把每个字串提出来建\(trie\),光是提出来这个操作就已经T飞了
做法:建后缀自动机,我们知道\(parent\)树是个\(DAG\)图就可以进行\(topsort\)操作,我们按长度升序后倒序处理往\(parent\)上传
题目2:给出一个字符串\(S\),找出一系列子串\(a_1\)~\(a_k\),\(a_i\)在\(a_{i+1}\)出现了至少两次
做法:首先分析后缀自动机中同一点代表的这些子串是不会相互出现两次(\(endpos\)集合相同)
字符串\(A\)出现在字符串\(B\)至少两次,所以\(A\)出现在parent树中\(B\)的祖先\(,设\)B\(的右端点位置在\)pos[B]\(
那\)[pos[B]-len[B]+len[A],pos[B]]\(里\)A\(作为末端点出现了至少两次
理解??\)pos[B]\(是右端点,\)pos[B]-len[B]\(为左端点,\)[pos[B]-len[B]+len[A]\(加上\)A\(的长度因为是\)A\(整个都在\)B\(内
而实际上作为后缀\)A\(右端点已经在\)pos[B]\(中出现过一次,所以我们只要求\)[pos[B]-len[B]+len[A],pos[B]-1]$内出现一次
而由于我们要求多次是否出现,所以线段树合并一下
题目三
\([l,r]\)在\([1,l)\)中出现过则可以花\(b\)花费压缩也可以花\(a\)压缩,否则只能花\(a\)压缩
添加\(i\)进去后,我们找到后面\(dp[j]\)由\(dp[i]\)转移过去,只要在\(SAM\)里跑就好了
题目四
建好自动机后,往下遍历,\(len≥k\)后则子树公共后缀都满足,染好色,区间查询就莫队