写在前面的话:各种乱翻别人博客的时候突然发现自己还没学这个玩意儿 〒▽〒,所以打算做一些题,写个小结。
\(\text{Update}\):事实上感觉自己还是不咋会,感觉 \(\rm dfa\) 的题都可以看作是 "\(\tt dp\) 套 \(\tt dp\)",我们需要做的,就是缩减里面那层 \(\tt dp\) 的状态。话是这么讲,题目却都是那么有技巧的。可能还需要多做多练吧。
agc022E - Median Replace
直接扔题解了:\(\mathcal{P}\text{ortal.}\)
CF1142D - Foreigner
首先考虑建一个朴素的 \(\rm dfa\):令 \(dp(i,j)\) 为末尾为 \(s_i\),标号为 \(j\),且能匹配上以 \(i\) 为结尾的子串的不充分数字是否存在。转移记 \(\text{nxt}(i,j)\) 为向标号为 \(i\) 的不充分数字后面加上 \(j\) 这个数字后得到的不充分数字的标号(当然也可能不存在),转移式就是
其中 \(j\bmod 11>s_i\)。当 \(s_i\ne 0\) 时可以在末尾加上长度为 \(1\) 的数字。
如何计算 \(\text{nxt}(i,j)\) 呢?事实上,先将 \([1,9]\) 中的数塞进一个 queue 里,然后从队头取元素直接拓展插入队尾,你会发现 queue 中元素仍然是递增的!也就是说,对于标号 \(i\) 拓展的数字,标号一定大于标号 \([1,i)\) 拓展的数字,所以假设在 \(i\) 后添加 \(c\),\(c\) 的标号为
但是这样直接跑状态数肯定是爆炸的,我们考虑优化 \(\rm dfa\) 的大小:可以发现,标号 \(i\) 对后面一个数字的限制只和 \(i\bmod 11\) 有关!同时,\(i\) 和 \(i\bmod 11\) 得到的 \(f(x)=\displaystyle \sum_{j=1}^{x-1}(j\bmod 11)\) 甚至也是相同的。所以我们可以将所有标号取模 \(11\) 进行运算,那么在 \(i\) 后添加 \(c\),\(c\) 的标号为
于是成功地缩减了 \(\rm dfa\) 的大小,复杂度 \(\mathcal O(10n)\).
[TJOI 2018] 游园会
如何将 \(\rm lcs\) 计入状态?可以发现只计入 \(\rm lcs\) 的长度是没法进行递推的,我们需要刻画一个可递推的 \(\rm lcs\) 状态。考虑 \(\rm lcs\) 的转移(记收集串为 \(t\),兑奖串为 \(s\)):
观察到从 \(i-1\rightarrow i\) 时,我们只需用到 \(\text{lcs}(i-1,),\text{lcs}(i,)\) 这些函数值,而第二维的大小实际上长度极小,只有 \(15\),且 \(\text{lcs}(i,j)-\text{lcs}(i,j-1)\leqslant 1\),所以可以状压 \(\text{lcs}(i,)\) 这一行的 函数差分值 存入状态。这样就可以进行递推了。
于是设 \(dp(i,j,k)\) 为考虑到 \(s\) 的第 \(i\) 位,\(\text{lcs}(i,)\) 的状态为 \(j\),匹配 NOI
到了第 \(k\) 位。复杂度 \(\mathcal O(9nk2^k)\).
你可以把它称为 "\(\tt dp\) 套 \(\tt dp\)",也可以视作在 \(\rm dfa\) 上跑状态。
CF924F - Minimal Subset Difference
一些闲话:哈哈,学了这么久还是不会写数位 \(\tt dp\) 呢,我自鲨好了 இ௰இ.
记 \(f(x)\) 为数字 \(x\) 所能划分出的最小集合差。首先可以发现,数字 \(x\) 的 \(f\) 函数值只与 \(x\) 每个数位组成的多重集有关,于是本质不同的自变量只有 \(\displaystyle \binom{18+9}{9}\approx 5\cdot 10^6\)(把 \(18\) 位划分成 \([0,9]\) 之间的数字)个,可以直接搜索自变量,然后算出 \(f\) 函数值。
对于计算 \(f\) 函数值,具体地,设 \(x\) 每个数位的数字之和为 \(s(x)\),那么最小集合差实际上就是选取一个集合,其数位和 \(y\) 是满足 \(y\leqslant s(x)/2\) 的最大 \(y\),那么 \(f(x)=(s(x)-y)-y\)。考虑到 \(s(x)\leqslant 17\cdot 9\rightarrow s(x)/2\leqslant 77\),所以可以直接用 __int128
来做一个背包 \(\tt dp\),用 \(2^i\) 位表示是否存在一个集合其数位和为 \(i\).
现在考虑如何 \(\tt dp\).
先枚举 \(f\) 值,需要说明的是总有 \(f(x)\leqslant 9\) 成立。对于任一多重集 \(S\),如果某个数字出现了 \(\geqslant 2\) 次,我们总能将 \(2\) 次平分给两个集合,使所有数字出现次数 \(\leqslant 1\),此时最小集合差为零。现在考虑出现次数为 \(1\) 的数字(设其为 \(p_1,p_2,\dots,p_k\)),若 \(k\) 为偶数,那么 \(p_1,p_2\) 配对,\(p_3,p_4\) 配对,依此类推,显然不超过 \(9\);若 \(k\) 为奇数,增加 \(p_0=0\) 即可如偶数配对。
记 \(dp(i,j)\) 为还有 \(i\) 位要填,当前多重集状态为 \(j\) 的方案数即可 \(\tt dp\).
然而状态数还是太多了,因为 \(\tt dp\) 时是枚举所有出现在 \(x\) 中的数字,用去除此数字得到的状态进行转移,所以还要再记录一些状态。而 \(\tt dp\) 本身带 \(18\cdot 10\cdot 10\)(枚举数位,枚举数字,枚举 \(f\) 值)的常数,所以跑不过。
不过我们打表发现,\(f\) 函数值为 \(0,1\) 的数字极多,事实上,\(f\) 函数值 \(\geqslant 2\) 的 \(\tt dp\) 所涉及的状态数只有 \(32175\) 个,这是可以直接 \(\tt dp\) 的。
考虑到 \([l,r]\) 之间的数字个数是已知的,我们可以进行容斥。对于 \(k=1\) 的情况,只需用总数字个数减去 \(f(x)>1\) 的数字个数即可。对于 \(k=0\) 的情况,由于 \(f(x)\) 和 \(s(x)\) 的奇偶性相同,所以可以拆分成 —— 总数字个数减去 \(s(x)\) 为奇数的数字个数,再减去 \(f(x)\geqslant 2\) 且 \(f(x)\) 为偶数的数字个数。
另外为了加速,可以设 \(dp(i,j,k)\) 为还有 \(i\) 位要填,当前多重集状态为 \(j\),马上填的数小于 \(k\) 的方案数,预处理复杂度是一样的,但是询问复杂度降低了 \(10\) 的常数。
因为这题代码难写,所以还是丢一份代码:https://codeforces.com/contest/924/submission/164614760 (虽然这份代码也是照着别人写的 😵💫。