经典 DP 套 DP 问题及其多倍经验
经验 1:P4590 [TJOI2018] 游园会
经验 2:P10614 BZOJ3864 Hero meet devil
经验 3:ABC391G - Many LCS
强烈谴责 ABC 原题大赛。
题意
定义 LCS 表示 Longest Common Subsequence,即最长公共子序列。
给定字符串 \(S\),记其长度为 \(n\)。给定 \(m\),对于 \(i=0,1,2,\cdots,n\),有多少个长度为 \(m\) 的字符串 \(T\),求 LCS(S,T) 为 \(i\) 的字符串数量。
经验 1:字符集为 NOI
,要求 \(T\) 串中不能出现连续的 NOI
。\(n\le 15,m\le 1000\)。
经验 2:字符集为 ACGT
。\(n\le 15,m\le 1000\)。
经验 3:字符集为英文小写字母。\(n\le 10,m\le 100\)。
解法
考虑 LCS 问题的经典 DP 做法:设 \(f_{i,j}\) 表示字符串 \(A\) 的前 \(i\) 个字符与字符串 \(B\) 的前 \(j\) 个字符的 LCS 长度。
转移是 \(f_{i,j}=\max\{f_{i-1,j},f_{i,j-1},f_{i-1,j-1}+[A_i=B_j]\}\)。
这样的转移产生了一个性质:对于确定的 \(i\),\(0\le f_{i,j}-f_{i,j-1}\le 1\),即我们可以用二进制数表示 \(f_{i}\) 的差分数组。
由于第二维最大为 \(n\),状压的复杂度是可以接受的。设 \(F_{i,s}\) 表示 \(f\) 的第一维是 \(i\),\(f_i\) 的差分数组是 \(s\) 的方案数。
转移可以枚举下一个字符,是容易的。
并且转移只与 \(s\) 和下一个字符有关,与 \(i\) 无关,所以可以直接预处理。
这样总复杂度是 \(O((n+m)2^{n}\vert\Sigma\vert)\)
心路历程
刚开始先做的是 ABC 的题,WA 掉了,不知道为什么,感觉没问题。于是改了改交到游园会上面,游园会直接 WA 飞了,找了好久没发现问题。放了一段时间后,发现是如果新填入一个 N,我不会认为这是 NOI 的一个前缀,改了就 AC 了。
当时调试的时候在 LOJ 上面下载数据,就顺便交到 LOJ 上面了,没想到 WA 30。一样的代码,为什么结果不同呢?应该是有 UB。本地测试没有问题,Ubuntu 环境下也没问题,GMOJ 上面测试发现不开 O2 没问题,开了 O2 有问题。铁定是 UB 了。但是 UB 在哪呢?
原来我把字符串数组恰好开了字符串长度,导致最后终止符号 '\0'
越界了。
那么 ABC 的题过不了想必也是这个问题了,改掉之后果然过了。
松了一口气,应该最后一道经验题很快能拿下了。改完之后发现 TLE 了。这么慢的吗?本地造了一组数据完全没压力啊。遂去 VJ 上面试图寻找别的 OJ 试一试。找到 HDU,时限 8s,洛谷仅 3s。交了一下仍然 TLE,找了一个火车头,结果 WA 了。我真是越来越懵了。
后来看了一下题解,发现我的复杂度多了一个 \(n\),可以去掉,去掉之后洛谷就过了。再交到 HDU 上面,怎么就 WA 了呢?我百思不得其解。突然想起来我使用了 cerr
:在 HDU 上面标准错误流和标准输出流会混杂到一起。这就导致我 WA 了。至此所有经验题做完。
都怪 UB,一直对拍都拍不出来。