利用查表法求麻将向听数 知乎 九条可怜(吉如一老师)
原地址 https://zhuanlan.zhihu.com/p/31000381
九条可怜 应该是吉如一 , 人称吉老师
吉如一,北京大学信息科学技术学院2016级本科生,高中毕业于杭州学军中学。
现任北大ICPC(国际大学生程序设计竞赛)代表队学生教练,曾两次代表北京大学参加ICPC全球总决赛并获得一金一银。连续两年绩点排名第一;
曾获国家奖学金、唐立新奖学金等。
引言
只讨论手牌数量<=14的情况,超过不讨论。
和牌是向听数等于-1的特殊情况。
文本采用的麻将表示方法:
万:一二三四五六七八九
饼:①②③④⑤⑥⑦⑧⑨
索:123456789
字:东南西北白发中
编码
假设有14张的手牌(其他数量同理),可以将其编码成另一种形式,且不影响向听数的求解,步骤如下。
1、对手牌进行排序(牌种顺序可自行规定)。
2、计数。例如“一一一二三五六④⑤⑧99东南”,结果(key-value):
3、对2张不同的牌x,y,定义距离:
例如D(一,二)=1、D(二,四)=2、D(一,②)=∞、D(九,①)=∞、D(东,南)=∞。
4、取步骤2中的数量(value),并在每两个元素中插入它们key的距离。
例如“一一一二三五六④⑤⑧99东南”,得到序列:
5、将∞视为分割符,对分割后的子序列反转(reverse)、调整次序,不影响向听数S。
因此必须将这些序列表示成同一种序列。
定义min(或max)函数,重新计算每个子序列:
定义子序列比较方法,进行排序。
本文采用min函数,排序从大到小,直接比较序列连接组成的数字。
6、将步骤5的结果编码成64位整型,数量区间是[1,4],而距离∈{1,2,∞},因此可以使用4 bits存放距离(2 bits)和数量(2 bits),另外可以在距离比特位上定义结束符。编码最长的情况下,占用4*14=56(bits)。
7、编码结束。
枚举所有序列
对数量的枚举,即N(N∈{2,5,8,11,14})的加法分解问题,并且每个加数∈{1,2,3,4}。
对距离的枚举,限制∞分割后每个子序列的距离总和<=8。
另外有2个实际不存在的序列,排不排除影响不大。
最后一个子序列必在字牌中,对2枚字牌求距离总是∞。
枚举结果应是1292057种序列(已排除上面2个不存在的序列)。
对序列求向听数
DFS+剪枝。
定义:(为手牌数,)不存在雀头存在雀头刻子和顺子的数量和刻子和顺子的数量和(表示缺一张牌)
使用DFS求出S的最小值,即该序列的向听数。
DFS:先尝试取出雀头,然后尝试取出刻子和顺子,最后尝试取出刻子'和顺子'。
伪代码:
求向听数(序列) {
S = 8, C_max = 0
取雀头(序列, 牌数(序列), ref S, ref C_max, (牌数(序列)-2)/3)
return S
}
取雀头(序列, C_rem, ref S, ref C_max, K) {
foreach 雀头 in 取雀头迭代器(序列) {
取刻顺(序列-雀头, 0, C_rem-2, ref S, ref C_max, K, 1, 0)
}
取刻顺(序列, 0, C_rem, ref S, ref C_max, K, 0, 0)
}
取刻顺(序列, i, C_rem, ref S, ref C_max, K, P, G) {
if i==len(序列) {
取刻顺’(序列, 0, C_rem, ref S, ref C_max, K, P, G, 0)
return
}
g = 取一个刻子或顺子(序列)
if g!=null {
取刻顺(序列-g, i, C_rem-3, ref S, ref C_max, K, P, G+1)
}
取刻顺(序列, i+1, C_rem, ref S, ref C_max, K, P, G)
}
取刻顺’(序列, i, C_rem, ref S, ref C_max, K, P, G, G’) {
if S==-1 {return}
if G+G’>N {return}
C = 3*G+2*G’+2*P
if C_rem<C_max-C {return}
if C_rem==0 {
S = min(S, 2*(K-G)-G’-P)
C_max = max(C_max, C)
return
}
g = 取一个刻子或顺子’(序列)
if g!=null {
取刻顺’(序列, i, C_rem-2, ref S, ref C_max, K, P, G, G’+1)
}
取刻顺’(序列-序列[i], i+1, C_rem-牌数(序列[i]), ref S, ref C_max, K, P, G, G’)
}
写文件
将枚举结果排序,优先比较向听数,若向听数相同则比较编码后的64位整型值。优点是可以无需保存向听数,存储方法:[数量(-1向听)][序列(int64)...][数量(0向听)][序列(int64)...][数量(1向听)][序列(int64)...][...]。
进行差分编码,这步很重要,可以制造大量相同值,然后使用huffman编码或其他压缩算法进行压缩。如果不依赖第三方库,可以使用huffman编码,文件将被压缩到900KB左右(平均每个序列5.6bit)。使用zip可以压缩到200KB左右(平均每个序列1.2bit),使用rar可以压缩到100KB左右(平均每个序列0.6bit)。
GitHub
我的项目链接:https://github.com/ibukisaar/JapaneseMahjong
欢迎转帖 请保持文本完整并注明出处
技术博客 http://www.cnblogs.com/itdef/
B站算法视频题解
https://space.bilibili.com/18508846
qq 151435887
gitee https://gitee.com/def/
欢迎c c++ 算法爱好者 windows驱动爱好者 服务器程序员沟通交流
如果觉得不错,欢迎点赞,你的鼓励就是我的动力

