[ZJOI2019]麻將

題面

題意

共有n種麻將牌,給出一開始的13張麻將牌,問期望摸上來幾張牌後,與開始的13張牌組合後存在一個大小爲14的能和的子集.

做法

因爲要求期望的摸牌次數,我們可以將這個次數轉化爲i=134np(i),p(i)\sum_{i=13}^{4*n}p(i),p(i)表示總共有i張牌後仍然沒和的概率,而p(i)=cnt(i)/A4n13i13,cnt(i)p(i)=cnt(i)/A_{4*n-13}^{i-13},cnt(i)表示總共有i張牌後仍然沒和的排列數量.
爲了求這個值,我們需要記錄此時已有的牌的狀態來判斷它是否能和.現在我們考慮如何判斷一個集合是否能和.記dp[i][j][k]dp[i][j][k]表示當前考慮到第i種牌,以i2i-2爲開頭的順子有jj個,以i1i-1爲開頭的順子有kk個時的最大面子數,因爲順子數量不可能超過2(否則可以轉化爲3個刻子),因而後兩維的大小都是3.
用上述dp可以記錄一個集合中的最大面子數,這樣一個集合是否能和就只要記錄三個量:ctct(當前對子數)A[3][3],B[3][3]A[3][3],B[3][3]分別表示此時無對子和有對子的最大面子數,這樣的集合的種類其實很少.
然後考慮如何求cnt(i)cnt(i),可以設計第二個dp,dp[i][j][k]dp[i][j][k]表示當前考慮到第i種牌,此時牌的集合的狀態爲j,已經摸了k張牌的方案數,然後只要枚舉第i+1i+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];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]};
zz表示摸了幾張第i+1i+1種牌,yl[i]yl[i]表示開始13張牌中有幾張ii,qz[i]qz[i]yl[i]yl[i]的前綴和.
爲了節約空間,可以用滾動數組.

代碼

#include<bits/stdc++.h>
#define ll long long
#define N 110
#define MN 400
#define MM 4010
#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];
bool now,cur;
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;
	}
	void out()
	{
		ll i,j;
		for(i=0;i<3;i++)
		{
			for(j=0;j<3;j++)
			{
				cout<<dp[i][j]<<" ";
			}
			puts("");
		}
		puts("");
	}
	Zt tran(ll u)
	{
		ll i,j,k;
		Zt res;
//		cout<<u<<":\n";
//		out();
		for(i=0;i<3;i++)
		{
			for(j=0;j<3;j++)
			{
				if(dp[i][j]==-1) continue;
//				cerr<<i<<' '<<j<<endl;
				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);
				}
			}
		}
//		res.out();
//		for(i=1;i<=1000000000;i++);
		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;
	}
	void out()
	{
//		cout<<cnt<<endl;
		A.out(),B.out();
		puts("");
	}
	Mj tran(ll u)
	{
		ll i,j,k;
		Mj res;
//		out();
		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;
//		res.out();
//		puts("--------------------");
		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];
//	now.out();
	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);
	cout<<cm;return 0;
	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];
	now=1;
	dp[1][mm[st]][0]=1;
	for(i=0;i<n;i++)
	{
		swap(now,cur);
		memset(dp[now],0,sizeof(dp[now]));
		for(j=1;j<=cm;j++)
		{
			for(k=0;k<=4*i;k++)
			{
				if(!dp[cur][j][k]) continue;
				for(z=yl[i+1];z<=4;z++)
				{
					ll t=to[j][z];
					Add(dp[now][t][k+z],dp[cur][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[now][j][i]);
		}
//		cerr<<i<<" "<<sum<<endl;
		sum=sum*po(A(4*n-13,i-13),M-2)%M;
		Add(ans,sum);
	}
	cout<<ans;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章