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-KA−K,第二层倒着枚举当前选了jjj张(1=<j<=17)(1=<j<=17)(1=<j<=17),第三层dp[j]dp[j]dp[j]由dp[j−k]dp[j-k]dp[j−k]更新而来(1=<k<=4,j−k>=0)(1=<k<=4,j-k>=0)(1=<k<=4,j−k>=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,这下可以确信了
本文详细解析使用动态规划算法解决扑克牌组合问题,包括初始状态设定、递推公式构建以及边界条件处理,最终得出不同情况下组合数的计算方法,并通过代码实现验证结果。
85万+





