写在前面的话:各种乱翻别人博客的时候突然发现自己还没学这个玩意儿 〒▽〒,所以打算做一些题,写个小结。

\(\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\) 这个数字后得到的不充分数字的标号(当然也可能不存在),转移式就是

\[dp(i,\text{nxt}(j,s_i))\leftarrow dp(i-1,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\) 的标号为

\[9+\sum_{j=1}^{i-1}(j\bmod 11)+c+1 \]

但是这样直接跑状态数肯定是爆炸的,我们考虑优化 \(\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\) 的标号为

\[\left(9+i(i-1)/2+c+1\right)\bmod 11 \]

于是成功地缩减了 \(\rm dfa\) 的大小,复杂度 \(\mathcal O(10n)\).

[TJOI 2018] 游园会

如何将 \(\rm lcs\) 计入状态?可以发现只计入 \(\rm lcs\) 的长度是没法进行递推的,我们需要刻画一个可递推的 \(\rm lcs\) 状态。考虑 \(\rm lcs\) 的转移(记收集串为 \(t\),兑奖串为 \(s\)):

\[{\rm lcs}(i,j)=\max \begin{cases} {\rm lcs}(i-1,j-1)+1, & s_i= t_j \\ \max\{{\rm lcs}(i-1,j),{\rm lcs}(i,j-1)\}, & s_i\ne t_j\or s_i=t_j \end{cases} \]

观察到从 \(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 (虽然这份代码也是照着别人写的 😵‍💫。


$\text{To be continued...}$
posted on 2022-07-17 21:02  Oxide  阅读(203)  评论(0编辑  收藏  举报