I.导入
求长度为( ext{len})的包含给定连续子串( ext{T})的 0/1 串的个数。((|T|<=15))
通常来说这种题目应该立刻联想到状压 DP 与取反集——这样就不用考虑大量重复情况的容斥问题。设(f_{i,S})表示前(i)个字符、最后(|T|)个字符为(S)、不包含给定连续子串的情况数,状态转移方程简单不述。时间复杂度 (Theta(2^{|T|} ext{len}))。
II.巧妙利用利用失配树的序列DP
上述算法的时间复杂度相当高昂,当( ext{T})十分大或者不是一个 0/1 串而是一个字符串的时候完全不可做,并且大量的状态是无用的,因为我只关心不出现给定的字符串,然而相当多的状态不用转移已经注定了不可能出现给定字符串,用状态压缩表示表示整段的完整状态非常亏本。重磅的优化来了:
我们在 DP 状态的设置上模拟 KMP 算法的过程。设(f_{i,j})表示前(i)个字母、已经匹配到了模式串的(j)时有多少种情况。那么状态转移也依照 KMP 算法:枚举当前点的(j)并枚举下一个点填的字符,在失配树上找到下一个点的(j),把(f)累加过去,时间复杂度(Theta( ext{len}|T|^2s)),其中(s)是字符种类数。
当然不止可以在模式串上加强,还可以在文本串上加强:当( ext{len})是天文数字的时候,我们完全可以用矩阵快速幂优化上述过程,时间复杂度是(log)的。
III.例一:诗人小 K
(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))、轮廓线上的状态是( ext{S})、当前行与模式串的第一行匹配到(x)、与第二行匹配到(y)时的方案数。轮廓线的状态由 0/1 组成,表示它的后缀是否能够与模式串的第一行完全匹配。状态转移方程显然,前两维可以滚动掉,后面的维度需要大量的 memset (大概二十亿?),所以还是会超时。解决办法是把所有变量和数组都放进 register,这样刷表的速度就可以上天。这种解法就是这题目前的最优解法了。
V.总结
处理包含一种模式串的字符串总数,应该转化为求不包含它的字符串数,这个问题可以用 KMP 的思想大大优化。