【题解】[GXOI/GZOI2019]宝牌一大堆

[GXOI/GZOI2019]宝牌一大堆

\(\text{Solution:}\)

从 \(8kb\) 代码改到 \(11kb\) 最后封装到 \(5kb\) ……封装 yyds dwt yyds

学到最大的除了 \(dp\) 应该是调试技巧和封装的重要性了……

方程可能写的有点奇怪)看看能不能帮到和我一样设计状态的同学)

后面附带了一些注意事项。欢迎补充。

  • 「国士无双」

考虑枚举哪一张牌选两张即可,复杂度 \(O(13^2).\)

  • 「七对子」

由于七对子不能重复,所以考虑把每一张牌做对子的价值倍率都记录下来,选取最大的七个即可。

  • \(「3 \times 4 + 2」\)

这部分就需要 \(dp\) 了,暴力的复杂度显然不能接受。

考虑:设 \(dp[i][j][k][l][m]\) 表示当前是第 \(i\) 张牌,已经凑出 \(j\) 个面子, \(k\) 代表有没有雀头, 当前牌还剩 \(l\) 张,上一张牌还剩 \(m\) 张的最大得分。

注意这里是还剩下几张,所以转移的时候是用 \(i+1\) 牌的数量减去当前转移用掉的牌数。

以下令函数 \(\text{Getv(pos,num)}\) 表示第 \(pos\) 张牌有 \(num\) 张造成的宝牌收益。

凑一组刻子:

条件: \(Num_{i+1}>2\)

转移:

\[dp[i+1][j+1][k][Num_{i+1}-3][l]=\max\left\{ dp[i][j][k][l][m]\cdot Getv(i+1,3)\cdot {Num_{i+1}\choose 3}\right\} \]

凑一组顺子:

条件: \(l>0,m>0,Num_{i+1}>0\)

转移:

\[dp[i+1][j+1][k][Num_{i+1}-1][l-1]=\max\left\{ dp[i][j][k][l][m]\cdot Getv(i-1,1)\cdot Getv(i,1)\cdot Getv(i+1,1)\cdot\frac{ {Num_{i-1}\choose Num_{i-1}-m+1}\cdot {Num_i\choose Num_i-l+1}\cdot{Num_{i+1}\choose 1}}{{Num_i\choose l}\cdot {Num_{i-1}\choose m}}\right\} \]

凑两组顺子:

条件: \(l>1,m>1,Num_{i+1}>1\)

转移:

\[dp[i+1][j+2][k][Num_{i+1}-2][l-2]=\max\left\{ dp[i][j][k][l][m]\cdot Getv(i-1,2)\cdot Getv(i,2)\cdot Getv(i+1,2)\cdot\frac{ {Num_{i-1}\choose Num_{i-1}-m+2}\cdot {Num_i\choose Num_i-l+2}\cdot{Num_{i+1}\choose 2}}{{Num_i\choose l}\cdot {Num_{i-1}\choose m}}\right\} \]

凑一组雀头:

条件: \(k=0,Num_{i+1}>1\)

转移:

\[dp[i+1][j][k+1][Num_{i+1}-2][l]=\max\left\{dp[i][j][k][l][m]\cdot{Num_{i+1}\choose 2}\cdot Getv(i+1,2)\right\} \]

凑一组雀头带顺子一对:

条件: \(k=0,Num_{i+1}>2,l>0,m>0\)

转移:

\[dp[i+1][j+1][k+1][Num_{i+1}-3][l-1]=\max\left\{dp[i][j][k][l][m]\cdot Getv(i-1,1)\cdot Getv(i,1)\cdot Getv(i,3)\cdot\frac{{Num_{i+1}\choose 3}\cdot{Num_i\choose Num_i-l+1}\cdot {Num_{i-1}\choose Num_{i-1}-m+1}}{{Num_i\choose l}\cdot {Num_{i-1}\choose m}}\right\} \]

凑一组雀头带顺子两对:

条件: \(k=0,l>1,m>1,Num_{i+1}=4\)

转移:

\[dp[i+1][j+2][k+1][Num_{i+1}-4][l-2]=\max\left\{dp[i][j][k][l][m]\cdot Getv(i-1,2)\cdot Getv(i,2)\cdot Getv(i,4)\cdot\frac{{Num_{i+1}\choose 4}\cdot{Num_i\choose Num_i-l+2}\cdot {Num_{i-1}\choose Num_{i-1}-m+2}}{{Num_i\choose l}\cdot {Num_{i-1}\choose m}}\right\} \]

凑顺子带刻子:

条件: \(Num_{i+1}=4,l>0,m>0\)

转移:

\[dp[i+1][j+2][k][Num_{i+1}-4][l-1]=\max\left\{dp[i][j][k][l][m]\cdot Getv(i+1,4)\cdot Getv(i,1)\cdot Getv(i-1,1)\cdot\frac{{Num_i\choose Num_i-l+1}\cdot {Num_{i-1}\choose Num_{i-1}-m+1}\cdot{Num_i\choose 4}}{{Num_i\choose l}\cdot{Num_{i-1}\choose m}}\right\} \]

不选当前牌:

转移:

\[dp[i+1][j][k][Num_{i+1}][l]=\max\left\{dp[i][j][k][l][m]\right\} \]

注意事项:

  • 除法一定放在乘法最后,要不然先除可能会变成 \(0\) 。

  • 注意顺子能不能连起来,比如九筒和一索就不能连。

  • 注意转移的时候 \(j\) 代表的面子数量到底改不改动以及改多少。

  • 宝牌的数量不要想错,应该是 \(2^{num}.\)

  • 判断顺子的时候还要注意它是不是能连顺子的牌,即排除掉 “东西南北中白发” 七张牌。

  • 走国士无双的时候要判断一下能不能走。

  • 代码注意封装以减轻调试难度。

  • 读入的时候转数字编码要记录一下来方便写完后的代码调试以及细节处理。

  • \(dp\) 值为 \(0\) 的状态是不需要进入转移的。

  • 别忘了 「国士无双」 牌型的胡牌倍率 \(13\) 以及 「七对子」 胡牌牌型的倍率 \(7\) .

  • 开 \(\text{long long}.\)

代码:

#include<bits/stdc++.h> 
using namespace std;
#define int long long
int T,fac[20],card,num[50],vis[50];
vector<int>v;
inline int Max(int x,int y){return x>y?x:y;}
inline int Min(int x,int y){return x<y?x:y;}
void pre(){
	fac[0]=1;card=0;
	for(int i=1;i<=12;++i)fac[i]=fac[i-1]*i;
	for(int i=1;i<=34;++i)num[i]=4;
	for(int i=1;i<=34;++i)vis[i]=0;
}
int C(int x,int y){if(x<y)return 1;return fac[x]/fac[y]/fac[x-y];}
// num是牌的数量 vis是标记是不是宝牌 fac[]是阶乘 card是总牌数 
/*--------国士无双-----------*/
inline bool check_Tbp(int p){
	if(vis[p]&&num[p]>1)return true;
	return false;
}
int equal(){
	int ct=0;
	for(auto j:v){
		if(num[j]<1)return 0;
		ct+=num[j];
	}
	int ans=-1;
	for(auto j:v){
		if(num[j]<2)continue;
		// cout<<j<<" "<<num[j]<<" "<<vis[j]<<endl;
		int res=C(num[j],2);
		if(vis[j])res<<=2ll;
		for(auto k:v){
			if(k==j)continue;
			res*=num[k];
			if(vis[k])res<<=1ll;
		}
		ans=Max(ans,res);
//		if(res==ans)cout<<j<<" "<<num[j]<<endl;
	}
	return ans*13ll;
}
/*-------------七对子-------------*/
int val[50];
inline bool cmp(int A,int B){return A>B;}
int solve_seven(){
	for(int i=1;i<=34;++i){
		if(num[i]<2)continue;
		val[i]=C(num[i],2);
		if(vis[i])val[i]<<=2;
	}
	sort(val+1,val+34+1,cmp);
	int ans=1;
	for(int i=1;i<=7;++i)ans=ans*val[i];
	for(int i=1;i<=34;++i)val[i]=0;
	return ans*7;
}
/*--------------胡牌-------------*/
int dp[40][7][7][7][7]; 
//前i张牌,有j个面子,有没有雀头 i还剩几张,i-1还剩几张,
//dp[i][j][k][l][m]
bool ck(int x){
	if(x==28||x==29||x==30||x==31||x==32||x==33||x==34)return false;
	return true;
}
inline int Getv(int pos,int num){
	return vis[pos]?1LL<<num:1;
}
inline bool Ck(int x,int y,int z){
	return ck(y)&&ck(x)&&ck(z)&&(((z<=9)&&((x)>=1))||((z>9)&&(z)<=18&&x>9)||(z>18&&z<=27&&x>18));
}
int general(){
	dp[0][0][0][0][0]=1;
	for(int i=0;i<=33;++i)
		for(int j=0;j<=4;++j)
			for(int k=0;k<=1;++k)
				for(int l=0;l<=num[i];++l)//当前 
					for(int mm=0;mm<=num[i-1];++mm){//上一个 
						int cd=i+1;
						int numc=num[cd];
						int Len1=num[i];
						int Len2=num[i-1];
						if(!dp[i][j][k][l][mm])continue;
						if(numc>=3){//刻子 
							dp[i+1][j+1][k][numc-3][l]=Max(dp[i+1][j+1][k][numc-3][l],dp[i][j][k][l][mm]*Getv(i+1,3)*C(numc,3));
						}
						
						//顺子1 	
						if(l>0&&mm>0&&numc>0&&Ck(i-1,i,i+1)){
							dp[i+1][j+1][k][numc-1][l-1]=Max(dp[i+1][j+1][k][numc-1][l-1],dp[i][j][k][l][mm]*Getv(i-1,1)*Getv(i,1)*Getv(i+1,1)*numc*C(num[i-1],num[i-1]-mm+1)*C(num[i],num[i]-l+1)/C(Len2,Len2-mm)/C(Len1,Len1-l));
						}
						
						//顺子2 
						if(l>1&&mm>1&&numc>1&&Ck(i-1,i,i+1)){
							dp[i+1][j+2][k][numc-2][l-2]=Max(dp[i+1][j+2][k][numc-2][l-2],dp[i][j][k][l][mm]*Getv(i-1,2)*Getv(i,2)*Getv(i+1,2)*C(numc,2)*C(Len2,Len2-mm+2)*C(Len1,Len1-l+2)/C(Len2,Len2-mm)/C(Len1,Len1-l));
						}
						
						//单雀头 
						if(numc>=2&&!k)dp[i+1][j][k^1][numc-2][l]=Max(dp[i+1][j][k^1][numc-2][l],dp[i][j][k][l][mm]*Getv(i+1,2)*C(numc,2));
						
						
						//雀头带单顺子 
						if(!k&&numc>=3&&l>0&&mm>0&&Ck(i-1,i,i+1))
							dp[i+1][j+1][k^1][numc-3][l-1]=Max(dp[i+1][j+1][k^1][numc-3][l-1],dp[i][j][k][l][mm]*Getv(i+1,3)*Getv(i,1)*Getv(i-1,1)*C(Len2,Len2-mm+1)*C(Len1,Len1-l+1)/C(Len2,Len2-mm)/C(Len1,l)*C(numc,3));
						
						
						//雀头带双顺子 
						if(!k&&numc>=4&&l>1&&mm>1&&Ck(i-1,i,i+1))
							dp[i+1][j+2][k^1][numc-4][l-2]=Max(dp[i+1][j+2][k^1][numc-4][l-2],dp[i][j][k][l][mm]*Getv(i+1,4)*Getv(i,2)*Getv(i-1,2)*C(Len2,Len2-mm+2)*C(Len1,Len1-l+2)/C(Len2,Len2-mm)/C(Len1,Len1-l));
						//顺子带刻子
						if(numc>=4&&l>0&&mm>0&&Ck(i-1,i,i+1)){
							dp[i+1][j+2][k][numc-4][l-1]=Max(dp[i+1][j+2][k][numc-4][l-1],dp[i][j][k][l][mm]*Getv(i+1,4)*Getv(i,1)*Getv(i-1,1)*C(Len1,Len1-l+1)*C(Len2,Len2-mm+1)/C(Len1,Len1-l)/C(Len2,Len2-mm));
						} 
						
						
						//不选 
						dp[i+1][j][k][numc][l]=Max(dp[i+1][j][k][numc][l],dp[i][j][k][l][mm]);
					}
//	//----------------------------------------------------------------------------------------------------------------------------------
	int ans=-1;
	for(int i=0;i<=34;++i)
		for(int j=0;j<=4;++j)
			for(int k=0;k<=4;++k)
				ans=Max(ans,dp[i][4][1][j][k]);
	return ans;
	
}
void clear(){
	for(int i=0;i<=35;++i)
		for(int j=0;j<=5;++j)
			for(int k=0;k<=1;++k)
				for(int l=0;l<=5;++l)
					for(int mm=0;mm<=5;++mm)
						dp[i][j][k][l][mm]=0;
}
char getcha(){char x;cin>>x;return x;}
int Readchar(){
	char ch=getcha();
	if(ch=='0')return 0;
	if(isdigit(ch)){
		char c=getcha();
		if(c=='m')return (int)ch-'0';
		if(c=='p')return (int)ch-'0'+9;
		if(c=='s')return (int)ch-'0'+18;
	}
	else{
		switch(ch){
			case 'E':return 28;
			case 'W':return 29;
			case 'S':return 30;
			case 'N':return 31;
			case 'Z':return 32;
			case 'B':return 33;
			case 'F':return 34;
		}
	}
	return 0;
}
void Init(){
	int TT;
	scanf("%lld",&TT);
	while(TT--){
		pre();
		do{
			int x=Readchar();
			if(x==0)break;
			num[x]--;
			if(x>27||x==1||x==9||x==10||x==18||x==19||x==27)card++;
		}while(1);
		do{
			int x=Readchar();
			if(x==0)break;
			vis[x]=1;
		}while(1);
		card=13*4-card;
		int Ans=Max(equal(),Max(solve_seven(),general()));
//		printf("%lld %lld %lld\n",equal(),solve_seven(),general());
		printf("%lld\n",Ans);
		clear();
	}
}
signed main(){
//	freopen("111.txt","r",stdin);
	v.push_back(1);
	v.push_back(9);
	v.push_back(10);
	v.push_back(18);
	v.push_back(19);
	v.push_back(27);
	v.push_back(28);
	v.push_back(29);
	v.push_back(30);
	v.push_back(31);
	v.push_back(32);
	v.push_back(33);
	v.push_back(34);
	Init();
	return 0;
}
上一篇:融合差分变异策略和自适应调整权重的改进蝴蝶优化算法


下一篇:MicroPython TPYBoard v102 无线红外遥控舵机(基于红外解/编码模块)