集合S取出n个元素其和为K个数统计
序
最近csrediscore的创作者在制作一个斗地主的机器人,他探讨性地给我出了一个问题——怎么样去统计手牌组合的可能性?
该问题算是比较复杂的,在不考虑癞子的情况下有火箭、炸弹、单牌、对牌、连对、三张牌、三带一、单顺、双顺、三顺、飞机带翅膀、四带二等等牌型。
以地主20张手牌为例:
- 20张手牌中能打出火箭次数在[0,1]中取值。
- 20张手牌中能打出炸弹次数在[0,5]中取值。
- 20张手牌中能打出单牌次数在[0,20]中取值——在不考虑其他玩家的情况下,最多出20次单牌。
- 依次类推…………
所以问题转换为每种牌型中选取任意次数构成N张手牌的情况有多少种?
问题分解一
为了更加简单一点描述问题,我继续对问题进行了简化。
从集合{1,2,3,4}中,取出一个元素和为10的个数统计
比如集合为:{1,2,3,4},和值为10;其中取法1,2,3,4和4,3,2,1等价。
因和值固定,且都为正数,所以每个元素的取出次数有限,可以得出结论如下图
问题分解二
1 | 从集合 |
由图可知,可以取出的元素组合情况为: 11 * 6 * 4 * 3=792种。
最终只需要在这792种方案中选取出和为10的记录。
罗列792种方案的可行性的过程叫做求笛卡尔积,以下给出代码实现C#版代码实现过程。
代码实现
求笛卡尔积扩展类
1 | public static class EnumerableEx |
创建数字集合类
1 | /// <summary> |
控制台Program类
1 | class Program |
最终运行结果如下
1 | 1: 2个3,和为6 1个4,和为4 |
总结
我现在依旧不知道20张手牌,各种牌型组合的可能性有多少种,但是将问题转化成{1,2,3,4}求和为10的这种方式已经将问题做了一个很好的分解。
只需要将DigitGroup再做一下相应的替换就可以计算出可能的牌型有多少种,该问题的求解已经不在该文的范畴类,有兴趣的读者可以尝试解决一下。
参考资料
集合S取出n个元素其和为K个数统计
http://www.60points.com/from_list_S_take_N_numbers_make_sum_K/