题面见:https://www.luogu.com.cn/problem/P5301

 

 

 

 

题解

枚举+DP

直接枚举七对子和国士无双的情况

3*4+2的情况就DP来做

因为杠子一定不会比顺子优(读者自证不难)

于是我们可以设f[i][j][k][l][m][n]表示

枚举到了第i种牌组成了j个面子、k个雀头,第i、i+1、i+2张牌分别用了l、m、n张所获得的最大分数

于是就可以转移了(刷表法):(其中cb(i,j)为第i种牌选j张的宝牌加成,所有的"="都更新取max)

f[i][j][k+1][l+2][m][n]=f[i][j][k][l][m][n]*\frac{C_{cnt[i]}^{l+2}*cb(i,2)}{C_{cnt[i]}^{l}} (雀头)

f[i][j+1][k][l+3][m][n]=f[i][j][k][l][m][n]*\frac{C_{cnt[i]}^{l+3}*cb(i,3)}{C_{cnt[i]}^{l}} (刻子)

f[i][j+1][k][l+1][m+1][n+1]=f[i][j][k][l][m][n] *\frac{C_{cnt[i]}^{l+1}*cb(i,1)}{C_{cnt[i]}^{l}}*\frac{C_{cnt[i+1]}^{m+1}*cb(i+1,1)}{C_{cnt[i+2]}^{m}} *\frac{C_{cnt[i+2]}^{n+1}*cb(i+2,1)}{C_{cnt[i+2]}^{n}}(顺子)

 

f[i+1][j][k][m][n][0]=f[i][j][k][l][m][n] (推到下一个状态)

转移的时候只刷有值的状态,m,n只用枚举到2,因为三个相同的顺子可以换成三个刻子,这样就可以过了

 

 

遇到这种麻将扑克题,不要直接想爆搜,仔细的分析一下状态,就可以用DP来做

像这种大码量DP题一定要仔细,把每一个细节想清楚在写,不要随随便便就开始写,也不要因为畏难而不敢写

 

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define LL long long
LL f[36][5][2][5][5][5],g[36],ans;
int cnt[40],C[5][5],pw[6];
bool vis[40],pd[40]={0, 1,1,1,1,1,1,1,0,0, 1,1,1,1,1,1,1,0,0, 1,1,1,1,1,1,1,0,0, 0,0,0,0,0,0,0};
char ch[4];
int num()
{
	if(ch[1]=='m')return ch[0]-48;if(ch[1]=='p')return ch[0]-48+9;if(ch[1]=='s')return ch[0]-48+18;
	if(ch[0]=='E')return 28;if(ch[0]=='S')return 29;if(ch[0]=='W')return 30;if(ch[0]=='N') return 31;
	if(ch[0]=='Z')return 32;if(ch[0]=='B')return 33;if(ch[0]=='F')return 34;
}
int cb(int x,int ct)
{
	if(!vis[x])return 1;
	else return pw[ct];
}
LL solve13()
{
	int a[14]={0,1,9,10,18,19,27,28,29,30,31,32,33,34};LL sum=1,ret=0;
	for(int i=1;i<=13;i++)sum=sum*C[cnt[a[i]]][1]*cb(a[i],1);
	if(!sum) return 0;
	for(int i=1;i<=13;i++)ret=max(ret,13ll*sum/C[cnt[a[i]]][1]*C[cnt[a[i]]][2]*cb(a[i],1));
	return ret;
}
LL solve7()
{
	int a[35];LL sum=1;
	for(int i=1;i<=34;i++)a[i]=C[cnt[i]][2]*cb(i,2);
	sort(a+1,a+35);
	for(int i=34;i>=28;i--)sum=1ll*sum*a[i];
	return 7ll*sum;
}
LL solve342()
{
	int i,j,k,l,m,n,ll,ml,nl;LL tmp,ret=0;
	memset(f,0,sizeof(f));memset(g,0,sizeof(g));
	f[1][0][0][0][0][0]=1;
	for(i=1;i<=34;i++){
		for(j=0;j<=4;j++){
			for(k=0;k<=1;k++){
				for(l=0,ll=cnt[i];l<=4;l++){
					for(m=0,ml=cnt[i+1];m<=2;m++){
						for(n=0,nl=cnt[i+2];n<=2;n++){
							tmp=f[i][j][k][l][m][n];
							if(!tmp) continue;
							if(l+2<=ll&&k<1)
								f[i][j][k+1][l+2][m][n]=max(f[i][j][k+1][l+2][m][n],tmp/C[ll][l]*C[ll][l+2]*cb(i,2));
							if(l+3<=ll&&j<4)
								f[i][j+1][k][l+3][m][n]=max(f[i][j+1][k][l+3][m][n],tmp/C[ll][l]*C[ll][l+3]*cb(i,3));
							if(l<ll&&m<ml&&n<nl&&pd[i] && j<4&&m<2&&n<2)
								f[i][j+1][k][l+1][m+1][n+1]=max(f[i][j+1][k][l+1][m+1][n+1],tmp
								/C[ll][l]*C[ll][l+1]*cb(i,1)
								/C[ml][m]*C[ml][m+1]*cb(i+1,1)
								/C[nl][n]*C[nl][n+1]*cb(i+2,1));
							f[i+1][j][k][m][n][0]=max(f[i+1][j][k][m][n][0],tmp);
							
							if(j==4&&k==1)g[i]=max(g[i],f[i][j][k][l][n][m]);
						}
					}
				}
			}
		}
		ret=max(ret,g[i]);
	}
	return ret;
}
int main()
{
	int T,i,j;scanf("%d",&T);pw[0]=1;
	for(i=0;i<=4;i++){
		C[i][0]=C[i][i]=1;pw[i+1]=2*pw[i];
		for(j=1;j<i;j++)C[i][j]=C[i-1][j]+C[i-1][j-1];
	}
	while(T--){
		ans=0;for(i=1;i<=34;i++)cnt[i]=4;
		memset(vis,0,sizeof(vis));
		while(1){scanf("%s",ch);if(ch[0]=='0') break;cnt[num()]--;}
		while(1){scanf("%s",ch);if(ch[0]=='0') break;vis[num()]=1;}
		ans=max(ans,solve342());
		ans=max(ans,solve13());
		ans=max(ans,solve7());
		printf("%lld\n",ans);
	}
}

 

 

 

判断是否可以构成顺子的时候把vis当成pd来用了,结果调到了凌晨1点,后来中午到机房一看,5min就看出来错误了

建议以后都不要深夜写代码调题,既爆肝又调不出来。。。。