1. 程式人生 > 其它 >【題解】[GXOI/GZOI2019]寶牌一大堆

【題解】[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\) 張的最大得分。

以下令函式 \(\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\} \]

程式碼:

#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;
}