不会打麻将的 OIer 不是好 ACMer。
虽然看完这篇博客你可能并不会变成雀神,但是你可能看到和麻将有关的题目就不会那么慌了。
麻将的一般规则本文就不再赘述,啥搜索引擎上都有。实在不行下个雀魂玩玩也是可以的。
麻将题一般有两种做法:搜索和 DP。
搜索:
大多数时候是给出一副牌,看至少需要再摸几张牌才能胡。
考虑枚举合法终态,然后用牌数的差更新答案。
因为刻子和雀头都是多张同一个牌,所以这一块可以贪心,即从四张相同到一张相同的牌依次去匹配刻子和雀头,为了方便,可以设 \(dp_{i,j,k,l}\) 表示有 \(i\) 个四张,\(j\) 个三张,\(k\) 个二张拼成 \(l\) 个面子加上一个雀头最少需要多少张牌,在搜索顺子的同时维护一下 \(i,j,k,l\) 的值,然后直接更新答案即可。
然后考虑怎么搜索顺子,我们事先把所有的顺子预处理出来,共 \(27\) 个,然后按层搜索枚举每个顺子有没有。这样复杂度是 \(O(27^4)\),显然不好。实际上,我们枚举的顺子在此刻至少要有两张牌存在于手牌中,因为如果只有一张,那么对额外牌数的贡献为 \(2\),而这张牌形成的刻子贡献的额外牌数也是 \(2\),这样可以把它算到刻子里面。
实际上这样优化下来判定是十分快的,博主认为其复杂度不会超过 \(10^4\)。
DP:
搜索写起来比较方便,但是搜索毕竟复杂度是指数级别的,它的速度还是慢了点,所以再介绍一种 DP 的做法。
依旧是考虑给出一副牌,看至少需要再摸几张牌才能胡。
设 \(f_{i,0/1/2,0/1/2,0/1/2/3/4,0/1}\) 表示考虑前 \(i\) 种牌,以 \(i-1\) 开始的顺子有 \(0/1/2\) 个,以 \(i\) 开始的顺子有 \(0/1/2\) 个,面子一共有 \(0/1/2/3/4\) 个,有 \(0/1\) 个雀头的最小花费。转移的时候枚举下一种牌有几张即可。后面一堆东西看似有 \(90\) 个,但是实际上仅有 \(50+\) 个,且转移的时候转移边数不会很多。如果在这个上面再用上【搜索中的优化】,效率会更加高。(似乎这里不太严谨,但是跑得很快就对了)
从这也可以衍生出判定一个牌的集合中存不存在一个胡牌子集的方法,即设 \(f_{i,0/1/2,0/1/2,0/1}\) 表示考虑前 \(i\) 种牌,以 \(i-1\) 开始的顺子有 \(0/1/2\) 个,以 \(i\) 开始的顺子有 \(0/1/2\) 个,有 \(0/1\) 个雀头的最大面子数。如果有一个有雀头的状态的面子数到了 \(4\),那么就可行。
想必看完之后你就已经会打麻将了,[ZJOI2019] 麻将和[GXOI/GZOI2019]宝牌一大堆自然不在话下(其实后者真心就是只考了上述内容)。