题外话
我这种辣鸡放到浙江分分钟暴毙啊。
题目分析
牌是两两不同的,相同大小的牌也是不同的
假设现在有一个手头的牌的状态,则它可以表示为第 i i i个位置为第 i i i种牌取了多少张的字符串。
现在我要判断我手头能不能组成一组胡牌。
情况1:有七个对子,这个很好判断(设对子数为 c n t cnt cnt,则要求 c n t ≥ 7 cnt \geq 7 cnt≥7)。
情况2:可以拆成三面子一对子。这个有点难判断,所以在字符串已知的基础上做拆分法的DP。
麻将小知识:形如 ( i , i + 1 , i + 2 ) (i,i+1,i+2) (i,i+1,i+2)的三张牌叫“顺子”,形如 ( i , i , i ) (i,i,i) (i,i,i)的三张牌叫“刻子”,顺子和刻子统称为“面子”。
设 f ( i , 0 / 1 , j , k ) f(i,0/1,j,k) f(i,0/1,j,k)表示考虑了前 i i i种牌(前 i i i个字符),是否留有一对对子,我决定分出的 ( i − 1 , i , i + 1 ) (i-1,i,i+1) (i−1,i,i+1)型顺子有 j j j个,我决定分出的 ( i , i + 1 , i + 2 ) (i,i+1,i+2) (i,i+1,i+2)的顺子有 k k k个,此时已经凑出的最大面子数。
转移的话,首先已知了第 i + 1 i+1 i+1种牌有多少张,先留出 j + k j+k j+k张和前面的牌一起凑顺子(此时有 j j j个新面子诞生了),再枚举预定要划分出的 ( i + 1 , i + 2 , i + 3 ) (i+1,i+2,i+3) (i+1,i+2,i+3)型顺子有几个。剩下的牌,可以选择凑一个对子,或者凑一个刻子。
不难发现,同一种类型的顺子若出现三个,则可以当做三个刻子,所以 j , k j,k j,k都不会大于 2 2 2。
若这个字符串可以形成一套胡牌,说明 c n t ≥ 7 cnt \geq 7 cnt≥7,或 ∃ j , k \exists j,k ∃j,k使得 f ( n , 1 , j , k ) ≥ 4 f(n,1,j,k) \geq 4 f(n,1,j,k)≥4。
那么一个字符串“有用的状态”,只有 c n t cnt cnt和 f ( n , 0 / 1 , j , k ) f(n,0/1,j,k) f(n,0/1,j,k)这共计19个量,可以简化状态数。
现在不管字符串的长度了,只管转移,则可以建立一个自动机(麻将自动机!),用 c n t cnt cnt和去掉第一维的 f f f来表示一个状态,转移是添加一种牌(加0,1,2,3,4张),所有长度为 n n n的字符串就是从麻将自动机起点出发走 n n n步到达的状态。
若化简一下状态,强制要求 c n t cnt cnt大于 7 7 7了变 7 7 7, f f f大于 4 4 4了变 4 4 4,不记录有胡牌的状态,则状态数一共是2091种,借助map搜索得出。
接下来又可以在麻将自动机上DP了,设 f ( i , j , k ) f(i,j,k) f(i,j,k)表示考虑了 i i i种牌,当前状态在麻将自动机的节点 j j j上,一共从 P P P中摸了 k k k张牌出来,还没胡(既然在麻将自动机上则肯定没胡)的方案数。
设 g i g_i gi表示摸 i i i张牌还没胡的方案数,则答案为:
∑ i = 1 4 n − 13 g i ( i ! ) ( 4 n − 13 − i ) ! ( 4 n − 13 ) ! \frac{\sum_{i=1}^{4n-13} g_i (i!) (4n-13-i)!}{(4n-13)!} (4n−13)!∑i=14n−13gi(i!)(4n−13−i)!
代码
#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int mod=998244353,N=405;
int n,SZ,ans,a[105];
int fac[N],ifac[N],inv[N],tr[2100][5],f[2][2100][405];
struct data{int cnt,f[18];};
bool operator < (data A,data B) {
if(A.cnt!=B.cnt) return A.cnt<B.cnt;
for(RI i=0;i<18;++i) if(A.f[i]!=B.f[i]) return A.f[i]<B.f[i];
return 0;
}
map<data,int> mp;
int check(data x) {
if(x.cnt>=7) return 1;
for(RI i=9;i<18;++i) if(x.f[i]>=4) return 1;
return 0;
}
data trans(data x,int k) {
data y;
y.cnt=min(7,x.cnt+(k>=2));
for(RI i=0;i<18;++i) y.f[i]=-1;
for(RI i=0;i<=2;++i)
for(RI j=0;j<=2;++j) {
if(i+j>k) continue;
int f0=x.f[i*3+j],f1=x.f[9+i*3+j];
for(RI t=0;t<=k-i-j&&t<=2;++t) {
if(f0!=-1) {
if(k-i-j-t>=2) y.f[9+j*3+t]=max(y.f[9+j*3+t],f0+i);
y.f[j*3+t]=max(y.f[j*3+t],f0+i+(k-i-j-t>=3));
}
if(f1!=-1) y.f[9+j*3+t]=max(y.f[9+j*3+t],f1+i+(k-i-j-t>=3));
}
}
for(RI i=0;i<18;++i) if(y.f[i]>4) y.f[i]=4;
return y;
}
int build(data x) {
if(check(x)) return 0;
if(mp[x]) return mp[x];
mp[x]=++SZ;int k=SZ;
for(RI i=0;i<=4;++i) tr[k][i]=build(trans(x,i));
return k;
}
int qm(int x) {return x>=mod?x-mod:x;}
int C(int d,int u) {return 1LL*fac[d]*ifac[u]%mod*ifac[d-u]%mod;}
void DP() {
f[0][1][0]=1;
for(RI i=1,o=1;i<=n;++i,o^=1) {
for(RI j=1;j<=SZ;++j)
for(RI k=0;k<=n*4-13;++k) f[o][j][k]=0;
for(RI j=1;j<=SZ;++j)
for(RI k=0;k<=n*4-13;++k) {
if(!f[o^1][j][k]) continue;
for(RI t=a[i];t<=4;++t) {
if(!tr[j][t]) continue;
f[o][tr[j][t]][k+t-a[i]]=qm(f[o][tr[j][t]][k+t-a[i]]+
1LL*f[o^1][j][k]*C(4-a[i],t-a[i])%mod);
}
}
}
for(RI i=0;i<=n*4-13;++i) {
int g=0;
for(RI j=1;j<=SZ;++j) g=qm(g+f[n&1][j][i]);
ans=qm(ans+1LL*g*fac[i]%mod*fac[4*n-13-i]%mod);
}
ans=1LL*ans*ifac[4*n-13]%mod;
}
int main()
{
int x,y;data tmp;
tmp.cnt=tmp.f[0]=0;for(RI i=1;i<18;++i) tmp.f[i]=-1;
build(tmp);
scanf("%d",&n);
for(RI i=1;i<=13;++i) scanf("%d%d",&x,&y),++a[x];
fac[0]=1;for(RI i=1;i<=400;++i) fac[i]=1LL*fac[i-1]*i%mod;
inv[0]=inv[1]=1,ifac[0]=1;
for(RI i=2;i<=400;++i) inv[i]=1LL*(mod-mod/i)*inv[mod%i]%mod;
for(RI i=1;i<=400;++i) ifac[i]=1LL*ifac[i-1]*inv[i]%mod;
DP(),printf("%d\n",ans);
return 0;
}