扑克牌

扑克牌

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

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

法一:

注意到牌的数量很少,故可以暴力,于是方程中表现每张牌有几个花色,和排列最末尾的点数,然后就收到了(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];
}

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