zoukankan      html  css  js  c++  java
  • 扑克牌

    扑克牌

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

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

    法一:

    注意到牌的数量很少,故可以暴力,于是方程中表现每张牌有几个花色,和排列最末尾的点数,然后就收到了(O(>4^{13} imes 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]=sumegin{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
    ",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]=sumegin{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
    ",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] imes C_{a_{i+1}-1}^{k-1} imes C_{j}^{l} imes 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] imes 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
    ",hl,ans);
    	}
    	return 0;
    }
    il ull C(int n,int r){
    	if(n<0||r<0||n<r)return 0;return c[n][r];
    }
    
    
  • 相关阅读:
    oracle 的exists 的用法
    linux
    C++纯虚函数
    C++ 的虚析构函数
    C++ new
    C++点和箭头操作符用法区别
    static
    关于C的int
    互斥量mutex的简单使用
    nginx服务器屏蔽上游错误码
  • 原文地址:https://www.cnblogs.com/a1b3c7d9/p/11193006.html
Copyright © 2011-2022 走看看