有一副牌(除去大小王),从中拿出几张牌,当然是什么牌已经告诉你了,询问这些牌的排列方案数,并保证相同的点数不相邻。
解
这到题目倒有点像集合题,所以很抽象,笔者花了大量时间理解,特此整理并抄袭了网上的解法,希望能方便各位
法一:
注意到牌的数量很少,故可以暴力,于是方程中表现每张牌有几个花色,和排列最末尾的点数,然后就收到了(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[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[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个组放到空位的间隔中,间隔为两个数之间的空)
注:式子中的(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];
}