题解:P5279 [ZJOI2019] 麻将

· · 题解

前言

非确定性有限状态自动机 \to 确定性有限状态自动机 \to 爆搜有效状态 \to dp。

等等,为什么别的题解里都跑出了 2\,0923\,956 个状态?

题意

有一套只有一种花色的麻将,1\sim n 的数牌各有 4 张。将其随机打乱后,给定前 13 张牌,求最短的包含一个和牌的子集的前缀的长度的期望。和牌型包含基本型(四个面子和一个对子)和七对子(七个不同的对子)。

## 题解 首先转化一下题意。对于取值为正整数的随机变量 $X$,有 $\mathbb E[X]=\sum\limits_{i=0}^{+\infty} \Pr[X>i]$。换言之,我们只需要对于每个 $i$ 求出拿了 $i$ 张牌还没和的概率即可。 考虑先对于一个表示每种牌出现次数的序列 $\{c_i\}$ 设计一个判定和牌的非确定有限状态自动机。对于基本型,记录 $(i,j,k,t)$ 表示当前有 $i$ 个已经完成的面子,$j$ 个待完成的两面搭子,$k$ 个当前牌的单张和是否已经有对子。显然,同一种顺子有三个可以看成同一种刻子有三个,因此一定可以使 $j,k\le 2$,而因为总共最多四个面子,有 $i+j+k\le 4$,因此这样的状态一共有 $54$ 个。对于七对子,记录目前有 $0\sim 7$ 个对子,共有 $8$ 个状态。这就设计好了。 再考虑对于一个表示每种牌出现次数的序列 $\{c_i\}$,如何判定它有没有一个和牌的子集。直接考虑记录它的所有子集能走到的那些状态即可。看似可能有 $2^{62}$ 个状态,但是直觉告诉我们这些状态能否走到一定是非常关联的。(比如,能走到的两个状态一定牌数相同)实际上只有 $\boxed{c=1\,840}$ 个状态。 那么我们就可以 dp。$dp_{i,j,k}$ 表示前 $i$ 种数牌拿了 $j$ 个走到 $k$ 的子集数。注意转移要乘组合数。$i$ 这一维可以滚动掉。这样时间复杂度就是 $O(n^2S)$ 的,可以通过。 ## 代码 ```cpp #include <bits/stdc++.h> #define rep(i,n) for(int i=0,del##i##verme=int(n);i<del##i##verme;++i) #define rep1(i,n) for(int i=1,parano##i##a=int(n);i<=parano##i##a;++i) typedef long long ll; using namespace std; const ll mod1=998244353; int tp[2222],n0[2222],n1[2222],n2[2222],p[2222],cnt; int f[2][9][5][5][5]; ll trans(ll state,int ad) { ll ret=0; rep(i,cnt) if((state>>i)&1) { if(tp[i]==0) { int a=n0[i],x=n1[i],y=n2[i],z=p[i]; if(ad>=x+y) { int d=ad-x-y; a+=x+(d/3);x=y;y=d%3; if(f[0][a][x][y][z]!=-1) ret|=1ll<<f[0][a][x][y][z]; if(!z&&d>=2) { d-=2;if(f[0][n0[i]+n1[i]][n2[i]][d][1]!=-1) ret|=1ll<<f[0][n0[i]+n1[i]][n2[i]][d][1]; } } } else if(ad==2) { if(n0[i]!=7) ret|=2ll<<i; } else if(ad==0) { ret|=1ll<<i; } } return ret; } map<ll,int> id; int c;ll ft[3141]; int trs[5][3141]; bool ok[3141]; void dfs(ll x) { if(id[x]) return ; id[x]=++c;ft[c]=x; ll ret=0; rep(i,5) { ret|=trans(x,i); dfs(ret); } } ll C[405][405]; void init() { memset(f,-1,sizeof(f)); rep(a,5) rep(i,3) rep(j,3) rep(k,2) if(a+i+j<=4) { tp[cnt]=0;n0[cnt]=a; n1[cnt]=i;n2[cnt]=j;p[cnt]=k; f[0][a][i][j][k]=cnt++; } rep(i,8) { tp[cnt]=1;n0[cnt]=i; n1[cnt]=n2[cnt]=p[cnt]=0; f[1][i][0][0][0]=cnt++; } dfs((1ll<<f[0][0][0][0][0])|(1ll<<f[1][0][0][0][0])); rep1(i,c) { ll ret=0; rep(j,5) trs[j][i]=id[ret|=trans(ft[i],j)]; } rep1(i,c) { if(((ft[i]>>f[0][4][0][0][1])&1)||((ft[i]>>f[1][7][0][0][0])&1)) ok[i]=true; else ok[i]=false; } C[0][0]=1; rep1(i,400) { C[i][0]=C[i][i]=1; rep1(j,i-1) { C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod1; } } } ll qkpw(ll a,ll b) { ll r=1; while(b) { if(b&1)r=r*a%mod1; a=a*a%mod1; b>>=1; } return r; } int n,a,b; int cc[105]; ll dp[405][2005],nw[405][2005]; int main() { init(); cin>>n; rep1(i,13) { cin>>a>>b;++cc[a]; } dp[0][1]=1; rep1(i,n) { memset(nw,0,sizeof(nw)); rep(j,i*4-3) rep1(k,c) if(dp[j][k]) { for(int l=cc[i];l<=4;++l) { nw[j+l][trs[l][k]]+=dp[j][k]*C[4-cc[i]][4-l]; } } rep(j,i*4+1) rep1(k,c) dp[j][k]=nw[j][k]%mod1; } ll answer=0; for(int i=13;i<=4*n;++i) { ll cur=0; for(int j=1;j<=c;++j) { if(!ok[j]) cur+=dp[i][j]; } cur%=mod1; answer=(answer+cur*qkpw(C[4*n-13][i-13],mod1-2))%mod1; } cout<<answer<<endl; return 0; } ```