hihoCoder #1159 扑克牌

很早(大概两年前)就思考过这道题,然而当时并未解出。最近又把这道题翻出来,仍是看了题解才略知解法大义。现在我把这道题的解法以及我解题过程中的波折较详细地写下来,供后来人参考。


题目大意

一副不含王的扑克牌由52张牌组成,由红桃、黑桃、梅花、方块4组牌组成,每组13张不同的面值。现在给定52张牌中的若干张,请计算将它们排成一列,相邻的牌面值不同的方案数。

注:题目描述中的「面值」仅指点数,不包括花色。

分析

这道题是一个计数 DP 问题。这里给出两种解法,二者用不同的方法来看待(或称「考虑」)「排列给定的若干张扑克牌」这个过程。

解法一

此解法考虑「一张一张地排列给定的若干张扑克牌」的过程。

假设总共有 \(n\) 张牌,已经排列了一些,还剩下 \(m\) 张未排列。我们考虑「剩下的这 \(m\) 张牌(接着已经排列好的那些牌)还有多少种排列方案」。

显然,答案只跟「剩下的牌」和「已排好的最末一张牌」有关。

进一步思考,关于「剩下的牌」我们需要知道哪些信息?是剩下的牌的集合吗?不是的。我们只需要知道「剩下的牌中出现了 \(1\) 次、\(2\) 次、\(3\) 次、\(4\) 次的点数分别有多少个」就够了,而无需详察每一张剩下的牌的点数或花色。相应地,关于「已排好的最末一张牌」,只消知道「其点数在剩余牌中出现了几次」,亦不必关心其究竟为何种点数或花色。据此,不难得出下述 DP 状态:

DP 状态

\(\mathrm{DP}[a_1][a_2][a_3][a_4][last]\) :在「已排好的最末一张牌的点数」还剩 \(last\) 张(\(0\le last < 4\)),还剩 \(i\) 张的点数有 \(a_i\) 种(\(1\le i\le 4\))的情况下,剩下的牌还有多少种排列。

转移方程

\[\begin{align\*} \mathrm{DP}[a_1][a_2][a_3][a_4][last] = & (a_1 - \delta_{last,1})~\mathrm{DP}[a_1 - 1][a_2][a_3][a_4][0] \\\\ & + 2~(a_2 - \delta_{last, 2})~\mathrm{DP}[a_1 + 1][a_2 - 1][a_3][a_4][1] \\\\ & + 3~(a_3 - \delta_{last,3})~\mathrm{DP}[a_1][a_2 + 1][a_3 - 1][a_4][2] \\\\ & + 4~a_4~\mathrm{DP}[a_1][a_2][a_3 + 1][a_4 -1][3] \end{align\*} \]

其中,\(\delta_{i,j}\) 称作 Kronecker delta

\[\delta_{i,j} = \begin{cases} 1, & \text{if $i =j$;} \\\\ 0, & \text{if $i\ne j$.} \end{cases} \]

边界条件

\(\mathrm{DP}[0][0][0][0][0] = 1\)

两个失败的 DP 状态设计

我照着上述思路设计 DP 状态时有两次失败的尝试,现简述如下:
为简便计,我们用四元组 \((x_1, x_2, x_3, x_4)\) 表示尚未排列的扑克牌集合。「尚未排列的扑克牌集合」下文也简称「牌集」。

给定初始牌集 \((c_1, c_2, c_3, c_4)\)

尝试一

\(\mathrm{DP}[a_1][a_2][a_3][a_4][last]\) :满足「剩余 \(i\) 张的点数为 \(a_i\)\(1 \le i \le 4\)),且应经排好的牌的最后一张的点数为 \(last\) 」的「排列已取出的 \(\sum_{1\le i \le 4} c_i - a_i\) 张牌」的方案数。

边界条件:\(\mathrm{DP}[c_1][c_2][c_3][c_4][0] = 1\)

这个 DP 状态的问题在于:对每个输入都需要单独计算一次方案数,不同的输入之间不存在(?)重叠子问题,因而时间复杂度过高。

尝试二

\(\mathrm{DP}[a_1][a_2][a_3][a_4][last]\) 表示「(按题目要求)排列 \((a_1, a_2, a_3, a_4)\) 且最后一张牌的点数在牌集中出现了 \(last\) 次」的方案数。

边界条件:\(\mathrm{DP}[0][0][0][0][0] = 1\)

这种状态是无法转移的。

解法二

不难看出,扑克牌的花色是很容易处理的次要因素。在解法二中,我们将扑克牌的花色去掉,只留点数。这样,输入就可以看作 \(n\) 个字符。

此解法考虑「采用每次将所有相同字符插入当前字符串这种方式来排列给定的 \(n\) 个字符」的过程。

举例:给定字符串 aabbccd,将其按上述方法重新排列。
重排 #1
aa \(\to\) abab \(\to\) cabcab \(\to\) cabdcab
或者,重排 #2
aa \(\to\) baab \(\to\) ccbaab \(\to\) cdcbaab
当然,这第二种排法不满足「任意相邻字符不相同」。

一个长为 \(l\) 的字符串有 \(l+1\) 个「插入位」。比如,字符串 aabbc 的插入位可标示为 a a b b c _ 。
若某个「插入位」上的字符和前一个「插入位」上的字符相同则称此「插入位」为「重复位」。aabbc 的第二个和第四个「插入位」即为「重复位」。

我们可以用二元组 \((a, b)\) 来描述一个字符串 \(S\)状态\(a\)\(S\) 包含的不同字符的个数,\(b\)\(S\) 的「重复位」的个数。

借助上面的定义,下面给出另一种 DP 思路。

DP 状态

\(\mathrm{DP}[i][j]\) :排列前 \(i\) 种字符使得所得字符串的「重复位」的个数为 \(j\) 的方案数。

转移方程

设第 \(i\) 种字符有 \(c_i\) 个,前 \(i\) 种字符总数为 \(s_i\), 即 \(s_i = \sum_{1\le j\le i}c_j\) 。前 \(i\) 种字符组成长为 \(s_i\) 的字符串,有 \(s_i + 1\) 个「插入位」,设其状态为 \((i,j)\) 。考虑将 \(c_{i+1}\) 个第 \(i+1\) 种字符分成 \(k\) 组,其中 \(x\) 组插入到「重复位」上,\(k - x\) 组插入到非「重复位」上。不难得到下述转移方程:

\[\mathrm{DP}[i][ j ] \xrightarrow{\dbinom{c_{i+1} - 1}{ k - 1} \dbinom{j}{x} \dbinom{s_i + 1 - j}{k-x} } \mathrm{DP}[i+1][ j - x + c_{i+1} - k] \]

posted @ 2017-10-04 18:58  Pat  阅读(388)  评论(0编辑  收藏  举报