【GXOI/GZOI2019】宝牌一大堆(麻将DP)(贪心)

传送门


其实还有第三种麻将——四川麻将,规则和其他麻将都不太像,甚至在四川不同地区的规则也不一样。

而关于麻将胡牌的题,能够利用DP解决的,我们可以将这种技巧成为麻将DP

题解:

这篇题解里面用的是川麻的语言习惯。

首先注意到杠子是废的,我们永远不可能考虑用一个杠子代替一个刻子,因为它权值永远小于刻子。(没注意到没关系,反正写到DP里面杠子的转移就一行)

国士无双可以直接枚举重复的那个是什么 O ( 1 3 3 ) O(13^3) O(133)暴力算。

七对子显然直接选权值最大的7个就行了。

那么考虑普通胡牌。

同类型的顺子不可能出现两次以上,如果出现了三次,我们可以用三个刻子代替。

那么DP状态就很好设计了:

f [ i ] [ j ] [ k ] [ l ] [ 0 / 1 ] f[i][j][k][l][0/1] f[i][j][k][l][0/1]表示当前考虑了前 i i i种牌,已经确定组成的面子有 j j j个,以 i − 1 i-1 i1开头的顺子有 k k k个,以 i i i开头的顺子有 l l l个(注意这两种顺子是未确定的),是否包含一个对子,的最大权值。

转移大力分类讨论,注意不要把不该顺上的牌连上了。

代码里面的两个函数国士无双和七对子用的是日语罗马音。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

using std::cin;
using std::cout;
using std::cerr;
inline void ckmax(ll &a,ll b){a=a<b?b:a;}

cs ll c[5][5]={
	{1},
	{1,1},
	{1,2,1},
	{1,3,3,1},
	{1,4,6,4,1}
};

/*

万子:1~9
筒子:10~18
条子:19~27

东西南北中白发:28~34; 

*/

cs int kokushi[]={1,9,10,18,19,27,28,29,30,31,32,33,34};
ll a[39],b[39];
ll f[39][5][4][4][2];

inline ll kokushimuso(){
	for(int re i:kokushi)if(a[i]==0)return 0;
	ll ans=0,tmp;
	for(int re i:kokushi){
		if(a[i]<2)continue;tmp=1;
		for(int re j:kokushi)
		if(i==j)tmp*=c[a[j]][2]*b[j]*b[j];
		else tmp*=c[a[j]][1]*b[j];
		ckmax(ans,tmp);
	}
	return ans*13;
}

inline ll nanakumi(){
	static int val[34],cnt;
	cnt=0;
	for(int re i=1;i<=34;++i)
	if(a[i]>1)val[++cnt]=c[a[i]][2]*b[i]*b[i];
	if(cnt<7)return 0;ll ans=1;
	std::sort(val+1,val+cnt+1);
	for(int re i=cnt;i>cnt-7;--i)ans*=val[i];
	return ans*7;
}

inline ll dp(){
	memset(f,0,sizeof f);
	f[0][0][0][0][0]=1;
	for(int re i=0;i<34;++i)
	for(int re j=0;j<=4;++j)
	for(int re k=0;k<3&&k+j<=4;++k){
		if(k&&(i==9||i==18||i>=27))break;
		for(int re l=0;l<3&&l+k+j<=4;++l){
			if(l&&(i==9||i==18||i>=27))break;
			if(!f[i][j][k][l][0]&&!f[i][j][k][l][1])continue;
			ll v0=f[i][j][k][l][0],v1=f[i][j][k][l][1];
			for(int re t=0;t<=a[i+1]-k-l;++t){
				int tot=t+k+l;
				ll tr=c[a[i+1]][tot]*(b[i+1]==2?1ll<<tot:1);
				if(j+tot<=4&&t<3){
					ckmax(f[i+1][j+k][l][t][0],v0*tr);
					ckmax(f[i+1][j+k][l][t][1],v1*tr);
				}
				if(t>=2&&j+tot-2<=4){
					ckmax(f[i+1][j+k][l][t-2][1],v0*tr);
				}
				if(t>=3&&j+tot-2<=4){
					ckmax(f[i+1][j+k+1][l][t-3][0],v0*tr);
					ckmax(f[i+1][j+k+1][l][t-3][1],v1*tr);
				}
			}
		}
	}
	return f[34][4][0][0][1];
}

signed main(){
#ifdef zxyoi
	freopen("kokushi.in","r",stdin);
#endif
	std::ios::sync_with_stdio(false);
	int T;cin>>T;
	std::string s;
	while(T--){
		for(int re i=1;i<=34;++i)a[i]=4,b[i]=1;
		while(true){
			cin>>s;
			if(s.size()==1){
				if(s[0]=='0')break;
				switch(s[0]){
					case 'E':--a[28];break;
					case 'S':--a[29];break;
					case 'W':--a[30];break;
					case 'N':--a[31];break;
					case 'Z':--a[32];break;
					case 'B':--a[33];break;
					case 'F':--a[34];break;
				}
			}
			else switch(s[1]){
				case 'm':--a[s[0]-'0'];break;
				case 'p':--a[s[0]-'0'+9];break;
				case 's':--a[s[0]-'0'+18];break;
			}
		}
		while(true){
			cin>>s;
			if(s.size()==1){
				if(s[0]=='0')break;
				switch(s[0]){
					case 'E':b[28]=2;break;
					case 'S':b[29]=2;break;
					case 'W':b[30]=2;break;
					case 'N':b[31]=2;break;
					case 'Z':b[32]=2;break;
					case 'B':b[33]=2;break;
					case 'F':b[34]=2;break;
				}
			}
			else switch(s[1]){
				case 'm':b[s[0]-'0']=2;break;
				case 'p':b[s[0]-'0'+9]=2;break;
				case 's':b[s[0]-'0'+18]=2;break;
			}
		}
		ll ans=std::max(kokushimuso(),nanakumi());
		ckmax(ans,dp());
		cout<<ans<<"\n"; 
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值