一类巧妙利用利用失配树的序列DP

I.导入

求长度为\(\text{len}\)的包含给定连续子串\(\text{T}\)的 0/1 串的个数。(\(|T|<=15\))

通常来说这种题目应该立刻联想到状压 DP 与取反集——这样就不用考虑大量重复情况的容斥问题。设\(f_{i,S}\)表示前\(i\)个字符、最后\(|T|\)个字符为\(S\)、不包含给定连续子串的情况数,状态转移方程简单不述。时间复杂度 \(\Theta(2^{|T|}\text{len})\)。

II.巧妙利用利用失配树的序列DP

上述算法的时间复杂度相当高昂,当\(\text{T}\)十分大或者不是一个 0/1 串而是一个字符串的时候完全不可做,并且大量的状态是无用的,因为我只关心不出现给定的字符串,然而相当多的状态不用转移已经注定了不可能出现给定字符串,用状态压缩表示表示整段的完整状态非常亏本。重磅的优化来了:

我们在 DP 状态的设置上模拟 KMP 算法的过程。设\(f_{i,j}\)表示前\(i\)个字母、已经匹配到了模式串的\(j\)时有多少种情况。那么状态转移也依照 KMP 算法:枚举当前点的\(j\)并枚举下一个点填的字符,在失配树上找到下一个点的\(j\),把\(f\)累加过去,时间复杂度\(\Theta(\text{len}|T|^2s)\),其中\(s\)是字符种类数。

当然不止可以在模式串上加强,还可以在文本串上加强:当\(\text{len}\)是天文数字的时候,我们完全可以用矩阵快速幂优化上述过程,时间复杂度是\(log\)的。

III.例一:诗人小 K

一类巧妙利用利用失配树的序列DP

\(n \le 40\),\(1 \le x,y \le 5\),\(1 \le y \le 7\)。

反集转换肯定是要想到的,但是到了这一步后状态设置完全无法下手,三段的序列和你设什么状态都不是。注意到三段和的大小都非常小,连续的这么三段的长度的总数都非常少,总数只有\(2^{4+6+4}\)个。那么我们这些模式串全部枚举出来,问题就转化为不出现这些字串的字符串的个数。把模式串丢进一个 Trie 里面建出 AC 自动机,然后把每个终止点在失配树中的子树的结点都打上标记——不可转移,然后就跟上题一模一样了。

IV.例二:P3290 [SCOI2016]围棋

考虑这东西有了两列怎么办。我们使用一种巧妙的轮廓线 DP:设\(f_{i,j,S,x,y}\)表示到了点\((i,j)\)、轮廓线上的状态是\(\text{S}\)、当前行与模式串的第一行匹配到\(x\)、与第二行匹配到\(y\)时的方案数。轮廓线的状态由 0/1 组成,表示它的后缀是否能够与模式串的第一行完全匹配。状态转移方程显然,前两维可以滚动掉,后面的维度需要大量的 memset (大概二十亿?),所以还是会超时。解决办法是把所有变量和数组都放进 register,这样刷表的速度就可以上天。这种解法就是这题目前的最优解法了。

V.总结

处理包含一种模式串的字符串总数,应该转化为求不包含它的字符串数,这个问题可以用 KMP 的思想大大优化。

上一篇:Hive开发中使用变量的两种方法


下一篇:scrapy进阶(CrawlSpider爬虫__爬取整站小说)