[ZJOI2019]麻将

84 篇文章 2 订阅
48 篇文章 0 订阅

题面

题意

共有n种麻将牌,给出一开始的13张麻将牌,问期望摸上来几张牌后,与开始的13张牌组合后存在一个大小为14的能和的子集.

做法

因为要求期望的摸牌次数,我们可以将这个次数转化为 ∑ i = 13 4 ∗ n p ( i ) , p ( i ) \sum_{i=13}^{4*n}p(i),p(i) i=134np(i),p(i)表示总共有i张牌后仍然没和的概率,而 p ( i ) = c n t ( i ) / A 4 ∗ n − 13 i − 13 , c n t ( i ) p(i)=cnt(i)/A_{4*n-13}^{i-13},cnt(i) p(i)=cnt(i)/A4n13i13,cnt(i)表示总共有i张牌后仍然没和的排列数量.
为了求这个值,我们需要记录此时已有的牌的状态来判断它是否能和.现在我们考虑如何判断一个集合是否能和.记 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示当前考虑到第i种牌,以 i − 2 i-2 i2为开头的顺子有 j j j个,以 i − 1 i-1 i1为开头的顺子有 k k k个时的最大面子数,因为顺子数量不可能超过2(否则可以转化为3个刻子),因而后两维的大小都是3.
用上述dp可以记录一个集合中的最大面子数,这样一个集合是否能和就只要记录三个量: c t ct ct(当前对子数) A [ 3 ] [ 3 ] , B [ 3 ] [ 3 ] A[3][3],B[3][3] A[3][3],B[3][3]分别表示此时无对子和有对子的最大面子数,这样的集合的种类其实很少.
然后考虑如何求 c n t ( i ) cnt(i) cnt(i),可以设计第二个dp, d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示当前考虑到第i种牌,此时牌的集合的状态为j,已经摸了k张牌的方案数,然后只要枚举第 i + 1 i+1 i+1种牌摸了几张牌即可转移:
d p [ i + 1 ] [ t r a n s ( j , t ) ] [ k + z ] + = d p [ i ] [ j ] [ k ] ∗ A 4 − y l [ i + 1 ] z − y l [ i + 1 ] ∗ C k + z − q z [ i + 1 ] z − y l [ i + 1 ] ; dp[i+1][trans(j,t)][k+z]+=dp[i][j][k]*A_{4-yl[i+1]}^{z-yl[i+1]}*C_{k+z-qz[i+1]}^{z-yl[i+1]}; dp[i+1][trans(j,t)][k+z]+=dp[i][j][k]A4yl[i+1]zyl[i+1]Ck+zqz[i+1]zyl[i+1];
z z z表示摸了几张第 i + 1 i+1 i+1种牌, y l [ i ] yl[i] yl[i]表示开始13张牌中有几张 i i i, q z [ i ] qz[i] qz[i] y l [ i ] yl[i] yl[i]的前缀和.
为了节约空间,可以用滚动数组.

代码

#include<bits/stdc++.h>
#define ll long long
#define N 110
#define MN 400
#define MM 1610
#define M 998244353
using namespace std;

ll n,ans,cm,yl[N],qz[N],to[MM][5],jc[N*4],nj[N*4],dp[N][MM][4*N];

inline void Min(ll &u,ll v){if(v<u) u=v;}
inline void Max(ll &u,ll v){if(v>u) u=v;}
inline void Add(ll &u,ll v){u=(u+v)%M;}
struct Zt
{
	ll dp[3][3];
	Zt(){memset(dp,-1,sizeof(dp));}
	void init(){memset(dp,-1,sizeof(dp));}
	bool operator < (const Zt &u) const
	{
		ll i,j;
		for(i=0; i<3; i++)
		{
			for(j=0; j<3; j++)
			{
				if(dp[i][j]!=u.dp[i][j])
					return dp[i][j]<u.dp[i][j];
			}
		}
		return 0;
	}
	bool operator != (const Zt &u) const
	{
		ll i,j;
		for(i=0; i<3; i++)
		{
			for(j=0; j<3; j++)
			{
				if(dp[i][j]!=u.dp[i][j])
					return 1;
			}
		}
		return 0;
	}
	Zt tran(ll u)
	{
		ll i,j,k;
		Zt res;
		for(i=0; i<3; i++)
		{
			for(j=0; j<3; j++)
			{
				if(dp[i][j]==-1) continue;
				for(k=0; k<=min(2ll,u-i-j); k++)
				{
					Max(res.dp[j][k],dp[i][j]+k+(u-i-j-k)/3);
					Min(res.dp[j][k],4ll);
				}
			}
		}
		return res;
	}
};

inline Zt max(Zt u,Zt v)
{
	ll i,j;
	Zt res;
	for(i=0; i<3; i++)
	{
		for(j=0; j<3; j++)
		{
			res.dp[i][j]=max(u.dp[i][j],v.dp[i][j]);
		}
	}
	return res;
}

struct Mj
{
	Zt A,B;
	ll cnt;
	bool hu;
	Mj(){cnt=hu=0;}
	bool operator < (const Mj &u) const
	{
		if(cnt!=u.cnt) return cnt<u.cnt;
		if(A!=u.A) return A<u.A;
		return B<u.B;
	}
	Mj tran(ll u)
	{
		ll i,j,k;
		Mj res;
		res.B=B.tran(u);
		res.cnt=cnt;
		if(u>=2) res.cnt=min(res.cnt+1,7ll),res.B=max(res.B,A.tran(u-2));
		res.A=A.tran(u);
		if(res.cnt==7 || res.B.dp[0][0]==4) res.hu=1;
		return res;
	}
} st,mj[MM];
map<Mj,ll>mm;

inline ll A(ll u,ll v){return jc[u]*nj[u-v]%M;}
inline ll C(ll u,ll v){return jc[u]*nj[v]%M*nj[u-v]%M;}
inline ll po(ll u,ll v)
{
	ll res=1;
	for(; v;)
	{
		if(v&1) res=res*u%M;
		u=u*u%M;
		v>>=1;
	}
	return res;
}

inline ll in(Mj u)
{
	mm[u]=++cm;
	mj[cm]=u;
	return cm;
}

ll dfs(Mj now)
{
	if(mm.count(now)) return mm[now];
	if(now.hu) return 0;
	ll i,res=in(now);
	for(i=0; i<=4; i++) to[res][i]=dfs(now.tran(i));
	return res;
}

int main()
{
	st.A.dp[0][0]=0;
	ll i,j,k,z,p;
	jc[0]=1;
	for(i=1; i<=MN; i++) jc[i]=jc[i-1]*i%M;
	nj[MN]=po(jc[MN],M-2);
	for(i=MN-1; i>=0; i--) nj[i]=nj[i+1]*(i+1)%M;
	dfs(st);
	cin>>n;
	for(i=1; i<=13; i++)
	{
		scanf("%lld%*d",&p);
		yl[p]++;
	}
	for(i=1; i<=n; i++) qz[i]=qz[i-1]+yl[i];
	dp[0][mm[st]][0]=1;
	for(i=0; i<n; i++)
	{
		for(j=1; j<=cm; j++)
		{
			for(k=0; k<=4*i; k++)
			{
				if(!dp[i][j][k]) continue;
				for(z=yl[i+1]; z<=4; z++)
				{
					ll t=to[j][z];
					if(!t) continue;
					Add(dp[i+1][t][k+z],dp[i][j][k]*A(4-yl[i+1],z-yl[i+1])%M*C(k+z-qz[i+1],z-yl[i+1])%M);
				}
			}
		}
	}
	for(i=13; i<4*n; i++)
	{
		ll sum=0;
		for(j=1; j<=cm; j++)
		{
			if(mj[j].hu) continue;
			Add(sum,dp[n][j][i]);
		}
		sum=sum*po(A(4*n-13,i-13),M-2)%M;
		Add(ans,sum);
	}
	cout<<ans;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值