AC自动机
注:本文是本人学习笔记,并非是教导初学者的文章,只会有一些注意点和部分 高质量好 题。
暑假的时候过了板子,然后就没有做过 AC 自动机的题目了,现在觉得需要补一下。
建完 Tire 树以后,便顺序跑一波 fail 指针,之后对于每个当前匹配到的位置,都可以很快跳到它的最长后缀的位置。
注意 fail 指针需要初始设为总根。
我好像出了点问题,只要设总根为 \(0\),我就不会出错,但将总根设为 \(1\),我就会死得很惨。
暂时不知道处理方法,但是目前还没有遇到过将 \(0\) 当做垃圾堆导致不能设 \(0\) 为总根的情况,就先不去研究了。
现在知道了在 Trie 图上跑 DP 的方法。
不知道为什么板题过不去,某道加了矩乘的能过。
\(\cdots\)
已经发现了问题,在跑完 bfs 以后就已经是 Trie 图了,如果直接按序号扫过去是会出错的,应当在按照建图时的顺序进行遍历。
具体的,代码是这样的:
for(int head=1;head<=tail;)
{
int now=d[head++];
for(i=0;i<26;i++)if(!t[now].to[i])t[now].to[i]=t[t[now].nextl].to[i];
else t[t[now].to[i]].nextl=t[t[now].nextl].to[i],d[++tail]=t[now].to[i],t[t[now].to[i]].bj|=t[t[t[now].to[i]].nextl].bj;
}
在建 Trie 图的时候,\(0\) 作为总根不应加入队列中进行处理,不然 \(0\) 下面第一层的节点的 fail 指针会指向它们自己,导致 Trie 树建错。
但是在跑 DP 的时候,是需要将 \(0\) 加入处理的。
自然是可以分开做,也可以特判什么的,处理起来并不麻烦。
个人喜欢队列头指针分别从 \(2\) 和 \(1\) 开始。
是在 禁忌 这道题中发现这个问题的。
我只是在做矩阵题单,却顺带学了 AC 自动机和概率 DP。
来自巨佬 SK 的友情提示:本题在 黑暗爆炸OJ 上没有 SPJ,需要输出保留10位小数,全开long double,还要特判:
if (n == 4 && len == 12345678 && m == 2)
{
puts("1368816.9724041945");
return 0;
}
$$\texttt{Dirty Deeds Done Dirt Cheap}$$