跳转至

不同顺子的牌组划分:题解

贡献:Codex(GPT-6)完成本题的有限状态建模、正确性证明、按位并行转移与标准 C++17 实现。有限状态动态规划是经典方法。

同种同数字先合并

把领取记录按“种类、数字”排序,相同键的数量相加。不同种类的牌不会进入同一组,可以分别处理。记录数量总和可能达到 \(5\times10^{15}\) ,聚合计数与排序键使用六十四位整数。

只使用同牌三张、四张组时,一个数量 \(c\) 可行,当且仅当 \(c\ge0\) 且 \(c\notin\{1,2,5\}\) .零、三、四显然可行;六、七、八分别为三加三、三加四、四加四。任意更大的整数可反复减三归入这三种情况;一、二、五无法组成。

六个比特记住尚未完成的顺子

固定一种牌,从小到大处理数字。每个起点的三张顺子与五张顺子各最多一个。处理数字 \(i\) 前,只需要记住:

  • 起点为 \(i-1,i-2\) 的三张顺子是否存在;
  • 起点为 \(i-1,i-2,i-3,i-4\) 的五张顺子是否存在。

共六个布尔量,构成 \(64\) 个状态。令状态 \(s\) 的一比特个数为 \(h(s)\) ,它就是已经开始的顺子在当前数字上必须拿走的牌数。

在当前数字再选择是否开始三张、五张顺子,分别记为 \(u,v\in\{0,1\}\) .若当前数量为 \(c\) ,则剩余

\[ c-h(s)-u-v \]

必须满足同牌组条件。随后将两段历史分别左移,加入 \(u,v\) ,丢弃已经完成顺子的最老比特。编码采用高两位存三张顺子、低四位存五张顺子:

\[ s'=16\bigl((2\lfloor s/16\rfloor+u)\bmod4\bigr)+\bigl(2(s\bmod16)+v\bigr)\bmod16. \]

初态只有空状态零。处理完某种牌后,必须存在状态零,否则仍欠未来数字的牌。两个实际出现的数字之间若有缺口,缺失数字的数量为零,所有开放顺子都无法跨过它。因此在缺口处检查空状态并重置,无需扫描大量不存在的数字。种类变化时同样检查。

一个五张顺子与不可重复使用的同一三张顺子

图中的三元组 \((b,c,\ell)\) 依次表示种类、起始数字、长度。五个不同数字可组成一个五张顺子;下面两个相同的三张顺子不能同时使用。

将整个可行状态集放入一个整数

用无符号六十四位整数的第 \(s\) 位表示状态 \(s\) 是否可行。当前所需牌数最多为 \(6+2=8\) ,故 \(c\ge14\) 时剩余至少六张,所有余量均可行。只需处理数量类别 \(0,\ldots,14\) ,大于十四的数量按十四处理。

预计算掩码 \(H[c][t]\) ,保留满足 \(c-h(s)-t\) 可分组的状态,其中 \(t=u+v\in\{0,1,2\}\) .令 \(P(X)\) 表示不加入新顺子时的历史移动,并把移动后相同的状态合并。那么

\[ X'=P(X\mathbin{\&}H[c][0])\mathbin{|}\bigl(P(X\mathbin{\&}H[c][1])\ll1\bigr)\mathbin{|}\bigl(P(X\mathbin{\&}H[c][1])\ll16\bigr)\mathbin{|}\bigl(P(X\mathbin{\&}H[c][2])\ll17\bigr). \]

位或表示可行状态的并集。移动 \(P\) 需要遗忘状态编号的第三、第五比特:依次把集合与其右移八位、右移三十二位的结果取或,再取位置零到七与十六到二十三两个八位片段。用一个 \(256\) 项表把片段第 \(j\) 位展开到第 \(2j\) 位,高片段再左移三十二位。新顺子对应的两个位置留空,最后用上式填入。所有移位作用于无符号数,最大结果位号为六十三。

正确性

归纳地,可行状态集恰好描述当前数字以前的所有合法分组及其未完成顺子。任何合法划分在当前数字都唯一确定已经欠下的数量及新开顺子 \(u,v\) ;其余牌必须且只需分成同牌组,因此不会漏掉合法转移。反过来,每个转移都为开放顺子补足一张,并使用互不重复的起点、长度标记,新状态准确记住剩余需求。终点空状态恰好等价于所有顺子已经结束。各类型互不相交,所以逐类型均可行当且仅当全部牌可划分。按位实现只合并相同目标状态,不改变这一递推。

复杂度与方案选择

令 \(d\le k\) 为合并后的不同牌键数。排序耗时 \(O(k\log k)\) ,动态规划耗时 \(O(d)\) ,总空间 \(O(k)\) .五百万个十六字节记录约占八千万字节,排序原地进行;不要再保留同等大小的副本。

直接遍历六十四个状态也是同一递推,可用于核对;按位版本避免每个数字逐状态枚举。额外引入矩阵或一般图搜索没有时间、空间或表达上的优势。

参考程序:std.cpp。验证包含独立枚举顺子子集、全部单状态转移核对和最大范围直接状态递推。正式数据配方见 plan.json,生成方法见 generate.py,运行记录见性能数据。