[ZJOI2019]麻将

题目传送门

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

题意简述

  • 你有 (13) 张牌,还有 (4n-13) 张牌未摸。

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

  • (P) 的期望。

dp of dp

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

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

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

(dfrac{sum_{i=1}^{4n-13}g_i*i!(4n-13-i)!}{(4n-13)!}+1)

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

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

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

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

先不考虑七对子的情况。

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

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

七对子易特判。

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

(dp_{i,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。

原文地址:https://www.cnblogs.com/danieljiang/p/majiang.html