[ZJOI2019]麻将

1|0题目传送门

突然想起某个神仙教了我这道神仙题qwq那就巩固一下吧

0|1题意简述

  • 你有 13 张牌,还有 4n13 张牌未摸。

  • 对于未摸的所有排列,定义 P 为最小的 i 满足 Si 存在一个子集是胡的。

  • P 的期望。

1|0dp of dp

先考虑我们最后需要什么。

假设我们知道 gi 表示摸了 i 张牌后不胡的方案数。

那么我们可以将答案转化成下式。

i=14n13gii!(4n13i)!(4n13)!+1

因此,我们将答案转化为摸了 i 张牌不胡的方案数。

如何判断一副牌胡没胡呢(看出来)。

我们首先可以把相同的三个顺子视为三个刻子,这对结果没有影响。

那我们可以认为,相同的顺子最多只有两个

先不考虑七对子的情况。

我们设 fi,k,l,x 表示只考虑 [1,i] 之间的麻将,从 i1 开始的刻子k 张,从 i 开始的刻子l 张,是否已经选出了一个对子(即 x=0/1),最多能组成多少刻子。

那么能胡牌的条件就是 fn,k,l,1>3

七对子易特判。

所以把 18 维的状态全压进dp里就好啦。

dpi,j,k,S 表示用了 [1,i],一共选了 j 张牌,用 k 种牌可以组成对子,且18维dp的状态是 S 的方案数。

这个 S 看起来好大怎么办qaq,可以考虑提前搜出所有可能S

我们可以把所有合法的状态统一为状态 T

暴力发现不胡的状态**不超过 3000 **,可以直接莽dpqwq。

附上代码qwq。

/* *** 还要继续努力 成为一名烤咕学家哦 *** */ #include<bits/stdc++.h> using namespace std; typedef long long ll; const ll Mo=998244353; ll m,n,p,nxt[3005][5],tot,vis[105],C[5][5],fac[505],inv[505],dp[105][405][4005]; map<vector<ll>,ll>mp; template <typename T> void rd(T &x){ ll fl=1;x=0;char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') fl=-fl; for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0'; x*=fl; } void wr(ll x){ if(x<0) x=-x,putchar('-'); if(x<10) putchar(x+'0'); if(x>9) wr(x/10),putchar(x%10+'0'); } inline void Max(ll &x,ll y){x=max(x,y);} ll dfs(vector<ll> nw){ if(mp.find(nw)!=mp.end()) return mp[nw]; mp[nw]=++tot; ll ID=tot; for(ll i=0;i<=4;++i){ vector<ll> temp; temp.clear(); for(ll j=0;j<19;++j) temp.push_back(-1); temp[18]=nw[18]+(i>=2); for(ll a=0;a<3;++a) for(ll b=0;b<3;++b) for(ll k=0;k<3&&a+b+k<=i;++k){ if(nw[a+b*3]!=-1) Max(temp[b+k*3],nw[a+b*3]+a+(i-a-b-k)/3); if(nw[a+b*3+9]!=-1) Max(temp[b+k*3+9],nw[a+b*3+9]+a+(i-a-b-k)/3); if(a+b+k+2<=i&&nw[a+b*3]!=-1) Max(temp[b+k*3+9],nw[a+b*3]+a); } for(ll j=0;j<18;++j) temp[j]=min(temp[j],4ll); if(temp[9]>=4||temp[18]>=7) nxt[ID][i]=0; else nxt[ID][i]=dfs(temp); } return ID; } ll pre(){ vector<ll> nw; for(ll i=0;i<18;++i) nw.push_back(-1); nw.push_back(0); nw[0]=0; return dfs(nw); } ll pw(ll x,ll y){ ll s=1; while(y){ if(y&1) s=1ll*s*x%Mo; x=1ll*x*x%Mo;y>>=1; } return s; } int main(){ // freopen(".in","r",stdin); // freopen(".out","w",stdout); dp[0][0][pre()]=1; rd(n); for(ll i=0;i<=4;++i){ C[i][0]=1; for(ll j=1;j<=i;++j) C[i][j]=(C[i-1][j]+C[i-1][j-1])%Mo; } fac[0]=1; for(ll i=1;i<505;++i) fac[i]=1ll*fac[i-1]*i%Mo; for(ll i=0;i<505;++i) inv[i]=pw(fac[i],Mo-2); for(ll i=1,x,y;i<=13;++i){ rd(x),rd(y); vis[x]++; } ll ans=0; for(ll i=1;i<=n;++i) for(ll j=0;j<=4*(i-1);++j) for(ll k=1;k<=tot;++k) if(dp[i-1][j][k]) for(ll l=0;l<=4-vis[i];++l) (dp[i][j+l][nxt[k][vis[i]+l]]+=1ll*dp[i-1][j][k]*C[4-vis[i]][l]%Mo)%=Mo; for(ll j=0;j<=4*n-13;++j) for(ll k=1;k<=tot;++k) if(dp[n][j][k]) (ans+=1ll*fac[j]*fac[4*n-13-j]%Mo*dp[n][j][k]%Mo)%=Mo; wr(1ll*ans*inv[4*n-13]%Mo);puts(""); return 0; }

欢迎碾压qaq。

有没有大神带我玩awa。


__EOF__

本文作者Daniel Jiang
本文链接https://www.cnblogs.com/danieljiang/p/majiang.html
关于博主:JSOIer 高一萌新 求带/kel
版权声明:awa
声援博主:求赞/kel
posted @   Demoe  阅读(152)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· .NET Core 中如何实现缓存的预热?
· 三行代码完成国际化适配,妙~啊~
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
点击右上角即可分享
微信分享提示