[ZJOI2019]麻将
https://www.luogu.org/problemnew/show/P5279
题解
我们可以把一副牌看成一个字符串,字符串的第\(i\)位表示编号为\(i\)的牌有多少张。
注意一下胡牌的条件:1、四面+一对2、七对。
我们可以设计一个\(dp\)。
\(dp[i][j][k][0/1]\)表示已经做完了前\(i\)种权值,保留了\((i-1,i)\)有\(j\)对,除此之外还有\(k\)个\(i\),除此之外\(0\)不保留\(1\)保留了对子时,面子数的最大值。
转移的话枚举下一种牌型放几张牌,然后枚举转移之后的\(k\),那么剩下的\(x-k\)张牌需要全部和前面的\(i\)个对子组成面子,和前面的\(j\)个单的组成对,所以我们的\(i+j+k\)不能超过新来点的牌的总和。
还有我们注意到当\(j\)或者\(k\)超过\(2\)时是可以直接组成面子的,所以我们的一个\(dp\)数组时\(3\times 3\)的。
对于\(0\)和\(1\)的转移,注意到当新来的牌超过\(1\)张时是可以从\(0\)转移到\(1\)的。
注意到我们刚才的\(dp\)状态中没有提到第二种胡牌的方式,这个我们可以在\(dp\)值中加上一个对子的个数来判断。
这个\(dp\)的第一维可以滚掉,于是我们的\(dp\)状态就变成了,当前牌有多少对牌,还有两个转移矩阵。
我们可以把这个\(dp\)的转移看做一个自动机,然后用\(bfs\)的方法去扩展,对于胡了的牌我们开一个节点,所有胡牌的转移边全部连上去。然后我们发现这个状态量只有\(2092\)。
于是我们可以设\(f[i][j][k]\)表示做到前\(i\)种牌,从后面的牌中拿了\(j\)张,现在在自动机的\(k\)节点上。
\(dp\)完之后我们相当于求出了取出\(i\)张牌每胡的概率,那么期望次数就是。
代码
#include<bits/stdc++.h>
#define M 2100
#define N 402
using namespace std;
typedef long long ll;
const int mod=998244353;
ll dp[2][N][M],jie[N],ni[N];
int sum,nowsum,tot,a[N],n;
inline ll rd(){
ll x=0;char c=getchar();bool f=0;
while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return f?-x:x;
}
inline ll power(ll x,ll y){
ll ans=1;
while(y){if(y&1)ans=ans*x%mod;x=x*x%mod;y>>=1;}
return ans;
}
inline ll C(int n,int m){return jie[n]*ni[m]%mod*ni[n-m]%mod;}
inline ll calc(int now,int x,int y){
return dp[now][x][y]*jie[x]%mod*jie[nowsum-x]%mod;
}
inline void MOD(ll &x){x=x>=mod?x-mod:x;}
struct matrix{
int a[3][3];
matrix(){memset(a,-1,sizeof(a));}
inline bool operator !=(const matrix &b)const{
for(int i=0;i<3;++i)
for(int j=0;j<3;++j)
if(a[i][j]!=b.a[i][j])return 1;
return 0;
}
inline bool operator <(const matrix &b)const{
for(int i=0;i<3;++i)
for(int j=0;j<3;++j)
if(a[i][j]!=b.a[i][j])return a[i][j]<b.a[i][j];
return 0;
}
inline bool hu(){
for(int i=0;i<3;++i)
for(int j=0;j<3;++j)if(a[i][j]>=4)return 1;
return 0;
}
inline void add(matrix b,int num){
for(int i=0;i<3;++i)
for(int j=0;j<3;++j)if(~b.a[i][j])
for(int k=0;k<3&&i+j+k<=num;++k){
a[j][k]=max(a[j][k],min(4,b.a[i][j]+i+(num-i-j-k)/3));
}
}
};
struct node{
matrix p[2];
int cnt,ch[5];
node(){cnt=0;memset(ch,0,sizeof(ch));p[0]=p[1]=matrix();}
inline bool hu(){
if(cnt>=7)return 1;return p[1].hu();
}
inline bool operator <(const node &b)const{
if(cnt!=b.cnt)return cnt<b.cnt;
if(p[0]!=b.p[0])return p[0]<b.p[0];
return p[1]<b.p[1];
}
inline void boom(){
memset(ch,0,sizeof(ch));
p[0]=p[1]=matrix();cnt=-1;
}
friend inline node operator +(node a,int num){
if(a.hu()){a.boom();return a;}
node t;
t.p[0].add(a.p[0],num);
t.p[1].add(a.p[1],num);
t.cnt=a.cnt+(num>=2);
if(num>=2)t.p[1].add(a.p[0],(num-2));
if(t.hu())t.boom();
return t;
}
};
node nw[2100];
map<node,int>mp;
inline void bfs(int id){
for(int i=0;i<5;++i){
node x=nw[id]+i;
if(mp.find(x)==mp.end())nw[mp[x]=++tot]=x;
nw[id].ch[i]=mp[x];
}
}
inline void prework(int n){
jie[0]=1;
for(int i=1;i<=n;++i)jie[i]=jie[i-1]*i%mod;
ni[n]=power(jie[n],mod-2);
for(int i=n-1;i>=0;--i)ni[i]=ni[i+1]*(i+1)%mod;
tot=2;
nw[1].p[0].a[0][0]=0;
mp[nw[1]]=1;
nw[tot].cnt=-1;
mp[nw[tot]]=2;
bfs(1);
for(int i=3;i<=tot;++i)bfs(i);
}
int main(){
n=rd();
sum=n*4;
nowsum=sum-13;
prework(sum);
for(int i=1;i<=13;++i){
int x=rd(),y=rd();
a[x]++;
}
int now=1,pre=0;
dp[now][0][1]=1;
for(int i=1;i<=n;++i){
swap(now,pre);
memset(dp[now],0,sizeof(dp[now]));
for(int j=0;j<=nowsum;++j){
for(int k=1;k<=tot;++k)if(dp[pre][j][k])
for(int l=0;l<=4-a[i];++l){
MOD(dp[now][j+l][nw[k].ch[l+a[i]]]+=dp[pre][j][k]*C(4-a[i],l)%mod);
}
}
}
ll ans=0;
for(int i=1;i<=tot;++i)if(i!=2){
for(int j=1;j<=nowsum;++j)MOD(ans+=calc(now,j,i));
}
ans=ans*ni[nowsum]%mod;
MOD(ans+=1);
cout<<ans;
return 0;
}