欢乐斗地主出牌方式统计

集合S取出n个元素其和为K个数统计这篇文章中提到了笛卡尔积的这种解法,但是以笛卡尔积的方式求解的时候,我中午点击运行,我晚上回来的时候才输出了168条牌型组合,然后一直没有响应,后来粗略估算了一下,需要在1.44E+18条左右数据中进行筛选,而选出总手牌数为20条相当于大海捞针,所以需要重新对问题进行思考。
先将问题明确定义——“在N张手牌中,可能的出牌方式有多少种?”

牌型分析

以地主20张手牌为例,20张手牌可能1个炸弹也没有,最多有5个炸弹。所以20张手牌,炸弹的取值范围的取值范围是[0,5];
而3张相同的牌带1张单牌也是四张牌,所以20张手牌,出三张相同的牌带一张单牌的次数也是[0,5];
如果手牌中存在66667777这样的四张牌,6667和7776这种打法和一个6炸和7炸是两种打牌的方式。
同样就算是以6,6,6,6,7,7,7,7这样的8张单牌一次打出去,欺负下家或上家手里只有3,4,5了也不是不可以。
所以66667777这样8张牌在不考虑输赢的情况下也是有很多种打法的。而巧的是,我们讨论的是不考虑输赢的情况。

20张手牌有几种打出去的方式呢?

最多以20次单牌的形式打出去。
最多以10次对子的形式打出去。
最多以3次三连对的形式打出去。
依次类推,有了以下的表格:

可能的牌型组合

具体实现

请下载C#版源代码,放到对应的控制台去执行即可。
这里列举几个示例结果:
手牌数为1

1
2
3
手牌数为1时,可能的打法情况为:
1: 牌型为:单张,每次消耗1,出了1次,共消耗掉1张牌
所有牌型列举完成

手牌数为2

1
2
3
4
5
手牌数为2时,可能的打法情况为:
1: 牌型为:王炸,每次消耗2,出了1次,共消耗掉2张牌
2: 牌型为:对子,每次消耗2,出了1次,共消耗掉2张牌
3: 牌型为:单张,每次消耗1,出了2次,共消耗掉2张牌
所有牌型列举完成

手牌数为3

1
2
3
4
5
6
手牌数为3时,可能的打法情况为:
1: 牌型为:3张牌,每次消耗3,出了1次,共消耗掉3张牌
2: 牌型为:单张,每次消耗1,出了1次,共消耗掉1张牌 牌型为:王炸,每次消耗2,出了1次,共消耗掉2张牌
3: 牌型为:单张,每次消耗1,出了1次,共消耗掉1张牌 牌型为:对子,每次消耗2,出了1次,共消耗掉2张牌
4: 牌型为:单张,每次消耗1,出了3次,共消耗掉3张牌
所有牌型列举完成

手牌数为4

1
2
3
4
5
6
7
8
9
10
手牌数为4时,可能的打法情况为:
1: 牌型为:炸弹,每次消耗4,出了1次,共消耗掉4张牌
2: 牌型为:3张牌带1张,每次消耗4,出了1次,共消耗掉4张牌
3: 牌型为:对子,每次消耗2,出了1次,共消耗掉2张牌 牌型为:王炸,每次消耗2,出了1次,共消耗掉2张牌
4: 牌型为:对子,每次消耗2,出了2次,共消耗掉4张牌
5: 牌型为:单张,每次消耗1,出了1次,共消耗掉1张牌 牌型为:3张牌,每次消耗3,出了1次,共消耗掉3张牌
6: 牌型为:单张,每次消耗1,出了2次,共消耗掉2张牌 牌型为:王炸,每次消耗2,出了1次,共消耗掉2张牌
7: 牌型为:单张,每次消耗1,出了2次,共消耗掉2张牌 牌型为:对子,每次消耗2,出了1次,共消耗掉2张牌
8: 牌型为:单张,每次消耗1,出了4次,共消耗掉4张牌
所有牌型列举完成

根据以上结果中的示例2和示例5可知,就算是7778也可以以777和8分两次打出去。
手牌数为5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
手牌数为5时,可能的打法情况为:
1: 牌型为:5张牌的顺子,每次消耗5,出了1次,共消耗掉5张牌
2: 牌型为:3张牌带1对,每次消耗5,出了1次,共消耗掉5张牌
3: 牌型为:3张牌,每次消耗3,出了1次,共消耗掉3张牌 牌型为:王炸,每次消耗2,出了1次,共消耗掉2张牌
4: 牌型为:对子,每次消耗2,出了1次,共消耗掉2张牌 牌型为:3张牌,每次消耗3,出了1次,共消耗掉3张牌
5: 牌型为:单张,每次消耗1,出了1次,共消耗掉1张牌 牌型为:炸弹,每次消耗4,出了1次,共消耗掉4张牌
6: 牌型为:单张,每次消耗1,出了1次,共消耗掉1张牌 牌型为:3张牌带1张,每次消耗4,出了1次,共消耗掉4张牌
7: 牌型为:单张,每次消耗1,出了1次,共消耗掉1张牌 牌型为:对子,每次消耗2,出了1次,共消耗掉2张牌 牌型为:王炸,每次消耗2,出了1次,共消耗掉2张牌
8: 牌型为:单张,每次消耗1,出了1次,共消耗掉1张牌 牌型为:对子,每次消耗2,出了2次,共消耗掉4张牌
9: 牌型为:单张,每次消耗1,出了2次,共消耗掉2张牌 牌型为:3张牌,每次消耗3,出了1次,共消耗掉3张牌
10: 牌型为:单张,每次消耗1,出了3次,共消耗掉3张牌 牌型为:王炸,每次消耗2,出了1次,共消耗掉2张牌
11: 牌型为:单张,每次消耗1,出了3次,共消耗掉3张牌 牌型为:对子,每次消耗2,出了1次,共消耗掉2张牌
12: 牌型为:单张,每次消耗1,出了5次,共消耗掉5张牌
所有牌型列举完成

限于篇幅,不再一一列举,根据程序列举出来的可能性如下图所示:

手牌为N时,可以的出牌方式统计

可知有17张手牌时,可以有1729种出牌方式。
可知有20张手牌时,可以有4815种出牌方式。
也许基于此,就有了赌徒说搏一搏,单车变摩托的说法。

注意,20张手牌的出牌方式并不能达到4815种,一个12张牌的顺子是12张牌,而再来两个炸弹8张牌,加起来也是20张牌。但是去掉3~A的12张牌,只能算出”2222”一个炸弹了。即”12张牌的顺子和2个炸弹”虽说符合代码的逻辑,但是并不符合现实中的牌型。换句话说,参考资料中的代码并没有考虑这种情况。

问题是还有哪些牌型组合在一起能满足总手牌数,但是不符合现实中的牌型呢?

总结

本以为在计算{1,2,3,4}中取出任意个数,与取出顺序无关时使用笛卡尔积的方式是很好的方式;结果在该问题中待处理数据竟达到了1.44E+18(1.44乘以10的18次方)条之多。
由此可知,问题并不是想当然就能够得到解决的,实践永远是检验真理的标准。
如果在每种牌型设置其对应的权重,所以在AI中的斗地主,如果能检测出手牌是10连对,或333444555666777带789JQ类型,得赶紧明牌不是么?

参考资料

作者

stone liu

发布于

2018-11-18

更新于

2024-09-06

许可协议

评论