【題解】[GXOI/GZOI2019]寶牌一大堆
阿新 • • 發佈:2021-07-10
\(\text{Solution:}\)
從 \(8kb\) 程式碼改到 \(11kb\) 最後封裝到 \(5kb\) ……封裝 yyds dwt yyds
學到最大的除了 \(dp\) 應該是除錯技巧和封裝的重要性了……
- 「國士無雙」
考慮列舉哪一張牌選兩張即可,複雜度 \(O(13^2).\)
- 「七對子」
由於七對子不能重複,所以考慮把每一張牌做對子的可能都記錄下來,選取最大的七個即可。
- \(「3 \times 4 + 2」\)
這部分就需要 \(dp\) 了,暴力的複雜度顯然不能接受。
考慮:設 \(dp[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;
}