loj3042/bzoj5600/洛谷P5279 [ZJOI2019]麻将 DP+麻将自动机

题外话

我这种辣鸡放到浙江分分钟暴毙啊。
我就是个辣鸡

题目分析

牌是两两不同的,相同大小的牌也是不同的

假设现在有一个手头的牌的状态,则它可以表示为第 i i i个位置为第 i i i种牌取了多少张的字符串。

现在我要判断我手头能不能组成一组胡牌。

情况1:有七个对子,这个很好判断(设对子数为 c n t cnt cnt,则要求 c n t ≥ 7 cnt \geq 7 cnt7)。

情况2:可以拆成三面子一对子。这个有点难判断,所以在字符串已知的基础上做拆分法的DP。

麻将小知识:形如 ( i , i + 1 , i + 2 ) (i,i+1,i+2) (i,i+1,i+2)的三张牌叫“顺子”,形如 ( i , i , i ) (i,i,i) (i,i,i)的三张牌叫“刻子”,顺子和刻子统称为“面子”。

f ( i , 0 / 1 , j , k ) f(i,0/1,j,k) f(i,0/1,j,k)表示考虑了前 i i i种牌(前 i i i个字符),是否留有一对对子,我决定分出的 ( i − 1 , i , i + 1 ) (i-1,i,i+1) (i1,i,i+1)型顺子有 j j j个,我决定分出的 ( i , i + 1 , i + 2 ) (i,i+1,i+2) (i,i+1,i+2)的顺子有 k k k个,此时已经凑出的最大面子数。

转移的话,首先已知了第 i + 1 i+1 i+1种牌有多少张,先留出 j + k j+k j+k张和前面的牌一起凑顺子(此时有 j j j个新面子诞生了),再枚举预定要划分出的 ( i + 1 , i + 2 , i + 3 ) (i+1,i+2,i+3) (i+1,i+2,i+3)型顺子有几个。剩下的牌,可以选择凑一个对子,或者凑一个刻子。

不难发现,同一种类型的顺子若出现三个,则可以当做三个刻子,所以 j , k j,k j,k都不会大于 2 2 2

若这个字符串可以形成一套胡牌,说明 c n t ≥ 7 cnt \geq 7 cnt7,或 ∃ j , k \exists j,k j,k使得 f ( n , 1 , j , k ) ≥ 4 f(n,1,j,k) \geq 4 f(n,1,j,k)4

那么一个字符串“有用的状态”,只有 c n t cnt cnt f ( n , 0 / 1 , j , k ) f(n,0/1,j,k) f(n,0/1,j,k)这共计19个量,可以简化状态数。

现在不管字符串的长度了,只管转移,则可以建立一个自动机(麻将自动机!),用 c n t cnt cnt和去掉第一维的 f f f来表示一个状态,转移是添加一种牌(加0,1,2,3,4张),所有长度为 n n n的字符串就是从麻将自动机起点出发走 n n n步到达的状态。

若化简一下状态,强制要求 c n t cnt cnt大于 7 7 7了变 7 7 7 f f f大于 4 4 4了变 4 4 4,不记录有胡牌的状态,则状态数一共是2091种,借助map搜索得出。

接下来又可以在麻将自动机上DP了,设 f ( i , j , k ) f(i,j,k) f(i,j,k)表示考虑了 i i i种牌,当前状态在麻将自动机的节点 j j j上,一共从 P P P中摸了 k k k张牌出来,还没胡(既然在麻将自动机上则肯定没胡)的方案数。

g i g_i gi表示摸 i i i张牌还没胡的方案数,则答案为:

∑ i = 1 4 n − 13 g i ( i ! ) ( 4 n − 13 − i ) ! ( 4 n − 13 ) ! \frac{\sum_{i=1}^{4n-13} g_i (i!) (4n-13-i)!}{(4n-13)!} (4n13)!i=14n13gi(i!)(4n13i)!

代码

#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int mod=998244353,N=405;
int n,SZ,ans,a[105];
int fac[N],ifac[N],inv[N],tr[2100][5],f[2][2100][405];
struct data{int cnt,f[18];};

bool operator < (data A,data B) {
	if(A.cnt!=B.cnt) return A.cnt<B.cnt;
	for(RI i=0;i<18;++i) if(A.f[i]!=B.f[i]) return A.f[i]<B.f[i];
	return 0;
}
map<data,int> mp;
int check(data x) {
	if(x.cnt>=7) return 1;
	for(RI i=9;i<18;++i) if(x.f[i]>=4) return 1;
	return 0;
}
data trans(data x,int k) {
	data y;
	y.cnt=min(7,x.cnt+(k>=2));
	for(RI i=0;i<18;++i) y.f[i]=-1;
	for(RI i=0;i<=2;++i)
		for(RI j=0;j<=2;++j) {
			if(i+j>k) continue;
			int f0=x.f[i*3+j],f1=x.f[9+i*3+j];
			for(RI t=0;t<=k-i-j&&t<=2;++t) {
				if(f0!=-1) {
					if(k-i-j-t>=2) y.f[9+j*3+t]=max(y.f[9+j*3+t],f0+i);
					y.f[j*3+t]=max(y.f[j*3+t],f0+i+(k-i-j-t>=3));
				}
				if(f1!=-1) y.f[9+j*3+t]=max(y.f[9+j*3+t],f1+i+(k-i-j-t>=3));
			}
		}
	for(RI i=0;i<18;++i) if(y.f[i]>4) y.f[i]=4;
	return y;
}
int build(data x) {
	if(check(x)) return 0;
	if(mp[x]) return mp[x];
	mp[x]=++SZ;int k=SZ;
	for(RI i=0;i<=4;++i) tr[k][i]=build(trans(x,i));
	return k;
}

int qm(int x) {return x>=mod?x-mod:x;}
int C(int d,int u) {return 1LL*fac[d]*ifac[u]%mod*ifac[d-u]%mod;}
void DP() {
	f[0][1][0]=1;
	for(RI i=1,o=1;i<=n;++i,o^=1) {
		for(RI j=1;j<=SZ;++j)
			for(RI k=0;k<=n*4-13;++k) f[o][j][k]=0;
		for(RI j=1;j<=SZ;++j)
			for(RI k=0;k<=n*4-13;++k) {
				if(!f[o^1][j][k]) continue;
				for(RI t=a[i];t<=4;++t) {
					if(!tr[j][t]) continue;
					f[o][tr[j][t]][k+t-a[i]]=qm(f[o][tr[j][t]][k+t-a[i]]+
						1LL*f[o^1][j][k]*C(4-a[i],t-a[i])%mod);
				}
			}
	}
	for(RI i=0;i<=n*4-13;++i) {
		int g=0;
		for(RI j=1;j<=SZ;++j) g=qm(g+f[n&1][j][i]);
		ans=qm(ans+1LL*g*fac[i]%mod*fac[4*n-13-i]%mod);
	}
	ans=1LL*ans*ifac[4*n-13]%mod;
}

int main()
{
	int x,y;data tmp;
	tmp.cnt=tmp.f[0]=0;for(RI i=1;i<18;++i) tmp.f[i]=-1;
	build(tmp);
	scanf("%d",&n);
	for(RI i=1;i<=13;++i) scanf("%d%d",&x,&y),++a[x];
	fac[0]=1;for(RI i=1;i<=400;++i) fac[i]=1LL*fac[i-1]*i%mod;
	inv[0]=inv[1]=1,ifac[0]=1;
	for(RI i=2;i<=400;++i) inv[i]=1LL*(mod-mod/i)*inv[mod%i]%mod;
	for(RI i=1;i<=400;++i) ifac[i]=1LL*ifac[i-1]*inv[i]%mod;
	DP(),printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值