LG P5279 [ZJOI2019]麻將
Description
九條可憐是一個熱愛打麻將的女孩子。因此她出了一道和麻將相關的題目,希望這題不會讓你對麻將的熱愛消失殆盡。
今天,可憐想要打麻將,但是她的朋友們都去下自走棋了,因此可憐只能自己一個人打。可憐找了一套特殊的麻將,它有$n(n\ge 5)$種不同的牌,大小分別為 $1$到 $n$,每種牌都有 $4$張。
定義面子為三張大小相同或者大小相鄰的麻將牌,即大小形如$i,i,i(1 \le i \le n)$或者$i,i+1,i+2(1 \le i \le n-2)$。定義對子為兩張大小相同的麻將牌,即大小形如$i,i(1 \le i \le n)$。
定義一個麻將牌集合 $S$是胡的當且僅當它的大小為 $14$且滿足下面兩個條件中的至少一個:
- $S$ 可以被劃分成五個集合 $S_1$至 $S_5$。其中 $S_1$為對子,$S_2$至 $S_5$為面子。
- $S$ 可以被劃分成七個集合 $S_1$至 $S_7$,它們都是對子,且對應的大小兩兩不同。
舉例來說,下列集合都是胡的(這兒只標記了大小):
- {1,1,1,1,2,3,4,5,6,7,8,9,9,9}
- {1,1,2,2,4,4,5,5,6,6,7,7,8,8}
- {1,1,2,2,3,3,4,4,5,5,6,6,7,7}
而下列集合都不是胡的:
- {1,1,1,2,3,4,5,6,7,8,9,9,9}
- {1,1,1,1,4,4,5,5,6,6,7,7,8,8}
- {1,1,1,2,3,4,5,6,7,8,9,9,9,11}
可憐先摸出了 $13$張牌,並把剩下的$4n-13$張牌隨機打亂。打亂是等概率隨機的,即所有$(4n-13)!$種排列都等概率出現。
對於一個排列$P$,可憐定義 $S_i$為可憐事先摸出的 $13$張牌加上 $P$中的前 $i$張牌構成的集合,定義 $P$的權值為最小的 $i$滿足 $S_i$存在一個子集是胡的。如果你對麻將比較熟悉,不難發現$P$的權值就是理論上的最早胡牌巡目數。注意到$n\ge 5$的時候,$S_{4n-13}$總是存在胡的子集的,因此 $P$的權值是良定義的。
現在可憐想要訓練自己的牌效,因此她希望你能先計算出 $P$的權值的期望是多少。
Solution
記$f[i][j][k][0/1]$為當前選到第$i$種麻將,有$j$個順子差最後一張,有$k$個順子差最後兩張,$0/1$代表是否有對子時的面子數量
列舉新來了幾張$i+1$的牌,求出加入這些牌之後的狀態,作為內層DP
記$dp[i][S]$為選$i$張牌,狀態為$S$的方案數,作為外層DP
DP套DP求解,最終答案為
$\sum_{i=13}^{4n} \sum_{S} \frac{dp[i][S](i-13)!(4n-i)!}{(4n-13)!}$
#include<iostream> #include<cstring> #include<cstdio> #include<queue> #include<cmath> #include<map> using namespace std; int siz,trans[5000][10],n,cnt[105],jc[505],C[500][500],dp[1000][5000],DP[1000][5000],ans; const int mod=998244353; struct Node { int cnt,f[3][3][2]; void clear() { cnt=0; memset(f,128,sizeof(f)); } }goal; queue<Node>q; map<Node,int>mp; inline int read() { int f=1,w=0; char ch=0; while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { w=(w<<1)+(w<<3)+ch-'0'; ch=getchar(); } return f*w; } bool operator < (Node a,Node b) { for(int i=0;i<3;i++) { for(int j=0;j<3;j++) { for(int k=0;k<2;k++) { if(a.f[i][j][k]!=b.f[i][j][k]) { return a.f[i][j][k]<b.f[i][j][k]; } } } } return a.cnt<b.cnt; } bool operator == (Node a,Node b) { for(int i=0;i<3;i++) { for(int j=0;j<3;j++) { for(int k=0;k<2;k++) { if(a.f[i][j][k]!=b.f[i][j][k]) { return false; } } } } if(a.cnt!=b.cnt) { return false; } return true; } Node operator + (Node v,int t) { if(v==goal) { return goal; } Node ans; ans.clear(); ans.cnt=v.cnt+(t>=2); if(ans.cnt>=7) { return goal; } for(int i=0;i<3;i++) { for(int j=0;j<3;j++) { for(int k=0;k<2;k++) { if(v.f[i][j][k]>=0) { for(int a=0;a<=min(i,t);a++) { for(int b=0;b<=min(j,t);b++) { for(int c=0;c<=1;c++) { for(int d=0;d<=1;d++) { if(a+b+c*2+d*3<=t) { ans.f[min(2,t-a-b-c*2-d*3)][a][k|c]=max(ans.f[min(2,t-a-b-c*2-d*3)][a][k|c],v.f[i][j][k]+b+d); } } } } } } } } } for(int i=0;i<3;i++) { for(int j=0;j<3;j++) { for(int k=0;k<2;k++) { ans.f[i][j][k]=min(ans.f[i][j][k],4); if(k==1&&ans.f[i][j][k]==4) { return goal; } } } } return ans; } void bfs() { Node st; st.clear(); st.f[0][0][0]=0; goal.clear(); mp[st]=++siz; mp[goal]=++siz; for(int i=0;i<=4;i++) { trans[2][i]=2; } q.push(st); while(q.size()) { Node st=q.front(); q.pop(); for(int i=0;i<=4;i++) { Node to=st+i; if(!mp[to]) { mp[to]=++siz; q.push(to); } trans[mp[st]][i]=mp[to]; } } } long long ksm(long long a,long long p) { long long ret=1; while(p) { if(p&1) { (ret*=a)%=mod; } (a*=a)%=mod; p>>=1; } return ret; } int main() { bfs(); n=read(); for(int i=1;i<=13;i++) { cnt[read()]++; read(); } jc[0]=C[0][0]=1; for(int i=1;i<=4*n;i++) { C[i][0]=1; jc[i]=1ll*jc[i-1]*i%mod; for(int j=1;j<=i;j++) { C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod; } } DP[0][1]=1; for(int o=1;o<=n;o++) { for(int i=0;i<=4*o;i++) { for(int x=1;x<=siz;x++) { dp[i][x]=DP[i][x]; DP[i][x]=0; } } for(int i=0;i<=4*o;i++) { for(int x=1;x<=siz;x++) { for(int k=cnt[o];k<=4;k++) { int to=trans[x][k]; if(to!=2) { (DP[i+k][to]+=1ll*C[4-cnt[o]][k-cnt[o]]*dp[i][x]%mod)%=mod; } } } } } for(int i=0;i<=4*n;i++) { int temp=0; for(int x=1;x<=siz;x++) { (temp+=DP[i][x])%=mod; } if(i>=13) { temp=1ll*temp*jc[i-13]%mod*jc[4*n-i]%mod; (ans+=1ll*temp*ksm(jc[4*n-13],mod-2)%mod)%=mod; } } printf("%lld\n",ans%mod); return 0; }[ZJOI2019]麻將