码迷,mamicode.com
首页 > 其他好文 > 详细

扑克牌

时间:2019-07-16 10:42:57      阅读:79      评论:0      收藏:0      [点我收藏+]

标签:记忆化   unsigned   不难   span   scan   一个   参考   ems   reg   

扑克牌

有一副牌(除去大小王),从中拿出几张牌,当然是什么牌已经告诉你了,询问这些牌的排列方案数,并保证相同的点数不相邻。

这到题目倒有点像集合题,所以很抽象,笔者花了大量时间理解,特此整理并抄袭了网上的解法,希望能方便各位

法一:

注意到牌的数量很少,故可以暴力,于是方程中表现每张牌有几个花色,和排列最末尾的点数,然后就收到了\(O(>4^{13}\times 13)\)的时间复杂度,显然不合理。

参考代码:

如果施主实在闲的慌,可以写一写,发给老朽,老朽定不胜感激

法二:(以下设\(a_i\)为点数i有的花色数,\(b_i\)为拥有i种花色数的点数,s为a的前缀和)

注意到考虑点数的花色过于复杂,不妨逆向思维,考虑花色数的点数数,于是设\(f[a][b][c][d]\)表示有a个点数有1张花色,b个点数有2两张花色,c个点数有3张花色,d个点数有4张花色的排列的方案数,因此有

\[f[a][b][c][d]=\sum\begin{cases}af[a-1][b][c][d]\\2b(f[a+1][b-1][c][d]-f[a][b][c-1][d])\\3c(f[a][b+1][c-1][d]-2(f[a+1][b][c-1][d]-f[a][b][c-1][d]))\\4d(f[a][b][c+1][d-1]-3(f[a][b+1][c][d-1]-2(f[a+1][b][c][d-1]-f[a][b][c][d-1])))\end{cases}\]

注:之所以+了还要-的缘故是因为你限定排列末尾摆了一个点数,排列倒数第二位有与末尾点数可能相同,要减去非法方案

边界:\(f[0][0][0][0]=1\)

答案:\(f[b[1]][b[2]][b[3]][b[4]]\)

参考代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#define il inline
#define ri register
#define ull unsigned long long
using namespace std;
int a[14],to[257],b[5];
ull dp[14][14][14][14];
il ull dfs(int,int,int,int);
int main(){
    char s[3];to['T']=10;
    to['J']=11,to['Q']=12;
    to['K']=13,to['A']=1;
    int lsy;scanf("%d",&lsy);
    dp[0][0][0][0]=1,dfs(0,0,0,13);
    for(int hl(1);hl<=lsy;++hl){
        int n;scanf("%d",&n);
        memset(a,0,sizeof(a)),memset(b,0,sizeof(b));
        for(int i(1);i<=n;++i){scanf("%s",s);
            if(s[0]>64)++a[to[s[0]]];
            else ++a[s[0]-48];
        }for(int i(1);i<14;++i)++b[a[i]];
        printf("Case #%d: %llu\n",hl,dp[b[1]][b[2]][b[3]][b[4]]);
    }
    return 0;
}
il ull dfs(int a,int b,int c,int d){
    ull &x(dp[a][b][c][d]);if(x)return x;
    if(a)x+=a*dfs(a-1,b,c,d);
    if(b)x+=2*b*(dfs(a+1,b-1,c,d)-dfs(a,b-1,c,d));
    if(c)x+=3*c*(dfs(a,b+1,c-1,d)-2*(dfs(a+1,b,c-1,d)-dfs(a,b,c-1,d)));
    if(d)x+=4*d*(dfs(a,b,c+1,d-1)-3*(dfs(a,b+1,c,d-1)-2*(dfs(a+1,b,c,d-1)-dfs(a,b,c,d-1))));
    return x;
}

注:方程没有明显的阶段性,所以不用循环转移,而是采取记忆化搜索的方式。

法三:

因为法二需要排除非法状态,很麻烦,原因在于它不知道它摆了是否合法,于是想到记录末尾摆的点数的花色数,然而没有达成预期目的,反套路地设\(f[a][b][c][d][r]\)表示a中点数有1张花色,b种点数有2张花色,c种点数有3张花色,d种点数有4张花色,排列的末尾的下一位要放的点数在排列中出现的花色数为r的排列方案数,因此不难有

\[f[a][b][c][d][r]=\sum\begin{cases}(a-(r==1))f[a-1][b][c][d][0]\\2(b-(r==2))f[a+1][b-1][c][d][1]\\3(c-(r==3))f[a][b+1][c-1][d][2]\\4df[a][b][c+1][d-1][3]\end{cases}\]

边界:\(f[0][0][0][0][0]=1\)

答案:\(f[b[1]][b[2]][b[3]][b[4]][0]\)

参考代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#define il inline
#define ri register
#define ull unsigned long long
using namespace std;
int a[14],b[5],to[257];
ull dp[14][14][14][14][4];
il ull dfs(int,int,int,int,int);
int main(){
    int lsy;char s[3];
    scanf("%d",&lsy),to['T']=10;
    to['J']=11,to['Q']=12;
    to['K']=13,to['A']=1;
    dp[0][0][0][0][0]=1;
    for(int hl(1);hl<=lsy;++hl){
        int n;scanf("%d",&n);
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        for(int i(1);i<=n;++i){
            scanf("%s",s);
            if(s[0]>64)++a[to[s[0]]];
            else ++a[s[0]-48];
        }for(int i(1);i<14;++i)++b[a[i]];
        printf("Case #%d: %llu\n",hl,dfs(b[1],b[2],b[3],b[4],0));
    }
    return 0;
}
il ull dfs(int a,int b,int c,int d,int r){
    ull &x(dp[a][b][c][d][r]);if(x)return x;
    if(a)x+=(a-(r==1))*dfs(a-1,b,c,d,0);
    if(b)x+=2*(b-(r==2))*dfs(a+1,b-1,c,d,1);
    if(c)x+=3*(c-(r==3))*dfs(a,b+1,c-1,d,2);
    if(d)x+=4*d*dfs(a,b,c+1,d-1,3);return x;
}

法四:

该题为排列问题,注意到排列问题和组合问题就一个阶乘之遥,为什么要考虑点数的花色数?然而继续反套路地设\(f[i][j]\)表示前i种点数有j个空位的排列方案数(注意,此时一种点数的牌无花色之分,定义空位为一对两个相同相邻的点数),注意到逆转移不好写方程,考虑顺转移,因此有(其中k表示把第i+1中点数分成k个非空组,l表示有l个组放到空位的间隔中,间隔为两个数之间的空)

\[f[i+1][j+a[i+1]-k-l]+=f[i][j]\times C_{a_{i+1}-1}^{k-1}\times C_{j}^{l}\times C_{s_{i+1}-j}^{k-l}\]

注:式子中的\(C_{a_{i+1}-1}^{k-1}\)含义为把\(a_{i+1}-1\)个无序元素划分成\(k\)组的方案数,可以根据竖线分组来解释,就是无序的\(a_{i+1}\)个0,有\(a_{i+1-1}\)个间隔(不包括端点),用\(k-1\)个竖线放在这些间隔中,因为保证非空,故与\(C_{a_{i+1}-1}^{k-1}\)一一对应。

边界:\(f[0][0]=1\)

答案:\(f[13][0]\times \prod_{i=1}^{13}a_i!\)

参考代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#define il inline
#define ri register
#define ull unsigned long long
using namespace std;
int a[14],to[257],S[14];
ull dp[14][40],c[60][60],jc[5];
il ull C(int,int);
int main(){
    char s[3];int n;
    int lsy;scanf("%d",&lsy);
    to['T']=10,to['J']=11,to['Q']=12,
        to['K']=13,to['A']=1,jc[0]=1;
    for(int i(1);i<5;++i)jc[i]=jc[i-1]*i;
    for(int i(0),j;i<60;++i)
        for(c[i][0]=1,j=1;j<=i;++j)
            c[i][j]=c[i-1][j]+c[i-1][j-1];
    for(int hl(1);hl<=lsy;++hl){
        scanf("%d",&n);
        memset(a,0,sizeof(a)),memset(s,0,sizeof(s));
        for(int i(1);i<=n;++i){scanf("%s",s);
            if(s[0]>64)++a[to[s[0]]];
            else ++a[s[0]-48];
        }for(int i(1);i<14;++i)S[i]=S[i-1]+a[i];
        memset(dp,0,sizeof(dp)),dp[0][0]=1;
        for(int i(0),j,k,l;i<13;++i)
            for(j=0;j<40;++j){if(!dp[i][j])continue;
                if(a[i+1])
                    for(k=1;k<=a[i+1];++k)
                        for(l=0;l<=k;++l){
                            if(j+a[i+1]-k-l<0)continue;
                            dp[i+1][j+a[i+1]-k-l]+=dp[i][j]*C(a[i+1]-1,k-1)
                                *C(j,l)*C(S[i]+1-j,k-l);
                        }else dp[i+1][j]+=dp[i][j];
            }ull ans(dp[13][0]);
        for(int i(1);i<14;++i)ans*=jc[a[i]];
        printf("Case #%d: %llu\n",hl,ans);
    }
    return 0;
}
il ull C(int n,int r){
    if(n<0||r<0||n<r)return 0;return c[n][r];
}

扑克牌

标签:记忆化   unsigned   不难   span   scan   一个   参考   ems   reg   

原文地址:https://www.cnblogs.com/a1b3c7d9/p/11193006.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!