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; }