斗地主不算花色算大小王,起初发到的17张牌有多少种?

本文详细解析使用动态规划算法解决扑克牌组合问题,包括初始状态设定、递推公式构建以及边界条件处理,最终得出不同情况下组合数的计算方法,并通过代码实现验证结果。

2015-02 初写

用动态规划算法来求,设dp[j]dp[j]dp[j]表示选了jjj张牌的种类数

起初dp[j]=0(j>0),dp[0]=1dp[j]=0(j>0),dp[0]=1dp[j]=0(j>0)dp[0]=1

第一层枚举牌的种类A−KA-KAK,第二层倒着枚举当前选了jjj(1=<j<=17)(1=<j<=17)(1=<j<=17),第三层dp[j]dp[j]dp[j]dp[j−k]dp[j-k]dp[jk]更新而来(1=<k<=4,j−k>=0)(1=<k<=4,j-k>=0)(1=<k<=4jk>=0)

如果大小王都不在,则有dp[17]dp[17]dp[17]种;如果大小王只有一个,则有dp[16]dp[16]dp[16]种;如果大小王都在,则有dp[15]dp[15]dp[15]种。所以ans=dp[17]+2×dp[16]+dp[15]ans=dp[17]+2\times dp[16]+dp[15]ans=dp[17]2×dp[16]+dp[15]

#include<cstdio>
#include<cstring>
int dp[20],ans;//dp[j]表示选j张牌的种类数
int main(){
    memset(dp,0,sizeof(int));
    dp[0]=1;
    for(int i=1;i<=13;i++){//A-K
        for(int j=17;j>=1;j--){
            for(int k=1;k<=4;k++){
                if(j-k>=0) dp[j]=dp[j]+dp[j-k];
            }
        }
    }
    ans=dp[17]+2*dp[16]+dp[15];
    printf("ans=%d\n",ans);
}

最终求得,斗地主不算花色算大小王,起初发到的17张牌有58684015种

2020-11 更新

时隔5年了,一想当初还是个稚嫩的大二学生,现在已经是个工作三年的润滑油了,上面这道题回顾了一下,一开始总觉得哪里不对,为什么 j 是倒着数的?这个很影响直观感觉,我们统计牌的时候,不应该是先从1开始算吗?dp[0]表示不选的方案为1,这个没问题,dp[1]很明显是13嘛,dp[2]呢?可以推导出来的,为什么 j 是从 17 开始 到 1 的,而不是从 1 开始到 17 的呢?

为了节省篇幅,我就不贴 for (int j = 1; j <= 17; j ++) 的代码了,总之改完后算出来结果是 85590206143,比 58684015 大很多,这已经超过了 int 范围了,算出来结果后我马上意识到问题所在,j 如果从 1 开始 到17,意味着结果中无法保证每种花色最多只选 4 个牌,是错误的计算方法

而 j 从 17 开始 到 1 是正确的,这里我就不具体画图说明了,只文字解释一下

  • dp[0] 表示不选,方案数为1
  • for (int i = 1; i <= 13; i ++) 表示选A-K某一种牌(在统计中A-K是等价的)
  • for (int j = 17; j >= 1; j --) 表示从17张牌开始,倒着枚举,配合后面的 for (int k = 1; k <= 4; k ++),希望从 dp[j - k] 的方案数更新于 dp[j] 的方案数(当然 j - k >= 0)
  • 对于A来说,也就是 i = 1 循环结束后,dp[0] = 1,dp[1] = 1,dp[2] = 1,dp[3] = 1,dp[4] = 1,表示5种情况
  • 对于2来说,也就是 i = 2 循环结束后
    • dp[0] = 1
    • dp[1] = 2 = 1 + 1(j=2,k=0)
    • dp[2] = 3 = 1 + 1(j=2,k=0)+ 1(j=1,k=1)
    • dp[3] = 4 = 1 + 1(j=3,k=0)+ 1(j=2,k=1)+ 1(j=1,k=2)
    • dp[4] = 5 = 1 + 1(j=4,k=0)+ 1(j=3,k=1)+ 1(j=2,k=2)+ 1(j=1,k=3)
    • dp[5] = 4 = 1(j=4,k=1)+ 1(j=3,k=2)+ 1(j=2,k=3)+ 1(j=1,k=4)
    • dp[6] = 3 = 1(j=4,k=2)+ 1(j=3,k=3)+ 1(j=2,k=4)
    • dp[7] = 2 = 1(j=4,k=3)+ 1(j=3,k=4)
    • dp[8] = 1 = 1(j=4,k=4)
  • 对于3、4、5、…、10、J、Q、K来说,一样可以如上列举

上面就是基本思路的展开说明,下面我们来通过搜索方法验证一下吧,由于工作中是用的JAVA了,就用JAVA来写了

package algorithm;

public class DouDiZhu1 {

    static long[] dp = new long[18];

    static int[] used = new int[14];

    static void clear() {
        for (int i = 0; i < 18; i ++) {
            dp[i] = 0;
        }
        for (int i = 1; i <= 13; i ++) {
            used[i] = 0;
        }
    }

    static void dfs(int dep, int last) {
        dp[dep] += 1;
        if (dep == 17) {
            return;
        }
        for (int i = last; i <= 13; i ++) {
            if (used[i] < 4) {
                used[i] += 1;
                dfs(dep + 1, i);
                used[i] -= 1;
            }
        }
    }

    public static void main(String[] args) {
        clear();
        dfs(0, 1);
        for (int i = 0; i < 18; i ++) {
            System.out.println("dp[" + i + "]=" + dp[i]);
        }
        long ans = dp[17] + 2 * dp[16] + dp[15];
        System.out.println("ans:" + ans);
    }
}

输出结果

dp[0]=1
dp[1]=13
dp[2]=91
dp[3]=455
dp[4]=1820
dp[5]=6175
dp[6]=18395
dp[7]=49205
dp[8]=120055
dp[9]=270270
dp[10]=566280
dp[11]=1111760
dp[12]=2056210
dp[13]=3598180
dp[14]=5978570
dp[15]=9459840
dp[16]=14289015
dp[17]=20646145
ans=58684015

搜索的结果也是58684015,这下可以确信了

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值