ZJOI2019 麻将

ZJOI2019 麻将

给定 \(n\ge 5\) 表示有 \(4\cdot n\) 张牌,然后给定 \(13\) 张基础牌,剩余的 \(4n-13\) 张牌随机打乱,求期望至少拿走多少张牌后可以胡牌。

胡牌当且仅当当前牌集存在一个大小为 \(14\) 的子集,满足可以拆分为:

  • 一个对子和 \(4\) 个面子

  • \(7\) 个大小不同的对子。

  • 对子即两张大小相同的牌,面子即三张大小连续的牌,或大小相同的牌。

  • \(n\le 100\)

\(\rm Sol:\)

先考虑一个子问题,我们已知一副手牌,希望知道他是否满足存在子集是胡牌。

我们发现第二个条件非常好判断,所以实际上我们只关注第一个条件,我们考虑通过一个 dp 来检查一副牌是否满足第一个条件。

\(f_{i,0/1}\) 表示当前 dp 到位置 \(i\),前面是否存在对子能得到的最大面子数。

我们发现对于加入 \(i\) 之后并进行 check,我们还需要知道 \(i-1,i-2\) 的剩余数量,显然我们不关注更远的位置,同时注意到顺子至多跑 \(2\) 个,三个会被认为是三个面子,所以还需要加两个维度来检查即可,每个维度的取值范围为 \(0\sim 2\),这样的话到一个确定的位置,一个确定的状态就能且仅能被 \(3\cdot 3\) 大小的矩阵描述了。

不过更方便的处理是设 \(f_{i,0/1,j,k}\) 表示预留了 \(j\)\((i-1,i)\),和 \(k\)\(i\) 时最大的面子数。

哦,你以为是 \(3\cdot 3\),其实是 \(2\cdot 3\cdot 3\),但是无论如何,据说合法的状态数只有 \(2092\) 个,那么将 \(18\) 个 dp 值压在一起就得到了状态数了。

请注意,这个合法的状态数是建立在我们转移以及自动机识别的时候不考虑 \(i\) 的情况下建立的,显然 \(i\) 在此处是无用的,我们只关心这 \(18\) 个 dp 数组的具体取值,以及每个状态识别一个元素 \(0,1,2,3,4\) 后会转移到的取值而已。

然后我们根据计算期望的经典 trick,只需要计算填涂了若干次后仍然合法的概率和即可,这样假设长度为 \(k\),对于答案的贡献显然就是 \(\frac{(k-13)!}{(4n-13)^{\underline{k}}}\)

然后我们考虑一个长度为 \(k\) 的字符串,其合法当且仅当给定串为其子集,同时没有遇到非法的状态,那么预处理一下转移,直接用 dp 统计方案数即可。

前面暴搜得到的状态数是 \(2091\),所以这里的复杂度为 \(\mathcal O(2091\cdot n^2\cdot 4)\)

\(Code:\)

#include<bits/stdc++.h>
using namespace std ;
#define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
#define rep( i, s, t ) for( register int i = (s); i <= (t); ++ i )
#define drep( i, s, t ) for( register int i = (t); i >= (s); -- i )
#define re register
#define int long long
int gi() {
	char cc = getchar() ; int cn = 0, flus = 1 ;
	while( cc < '0' || cc > '9' ) {  if( cc == '-' ) flus = - flus ; cc = getchar() ; }
	while( cc >= '0' && cc <= '9' )  cn = cn * 10 + cc - '0', cc = getchar() ;
	return cn * flus ;
}
const int P = 998244353 ; 
const int M = 2200 + 5 ; 
int n, cnt, idcnt, Ans, c[10][10], w[125], dp[2][425][M], vis[125][M], fac[425] ; 
struct node {
	int a[3][3] ; 
	bool operator < ( const node& x ) const {
		rep( i, 0, 2 ) rep( j, 0, 2 ) {
			if( a[i][j] != x.a[i][j] ) return ( a[i][j] < x.a[i][j] ) ;
		} return false ; 
	}
	bool operator != ( const node& x ) const {
		rep( i, 0, 2 ) rep( j, 0, 2 ) if( a[i][j] != x.a[i][j] ) return 1 ;	return 0 ; 
	}
	void init() { rep( i, 0, 2 ) rep( j, 0, 2 ) a[i][j] = -233 ; }
	void init2() { init(), a[0][0] = 0 ; }
	bool check() { rep( i, 0, 2 ) rep( j, 0, 2 ) if( a[i][j] >= 4 ) return 1 ; 	return 0 ; }
} ; 
node update( node S, int num ) {
	node res ; res.init() ; 
	rep( i, 0, 2 ) rep( j, 0, 2 ) {
		int d = S.a[i][j] ; if( d < 0 ) continue ; 
		for( re int k = 0; k <= 2 && ( i + j + k ) <= num; ++ k ) //当前位预留 i 个(i-2,i-1),j 个 (i-1),k 个 i
			res.a[j][k] = min( max( res.a[j][k], d + i + (num - i - j - k) / 3 ), 4ll ) ;
	} 
	return res ; 
} 
node mx( node S1, node S2 ) {
	node res ; res.init() ; 
	rep( i, 0, 2 ) rep( j, 0, 2 ) res.a[i][j] = max( S1.a[i][j], S2.a[i][j] ) ;
	return res ; 
}
struct mj {
	node f[2] ; int o ;
	void init() { f[0].init(), f[1].init(), o = 0 ; }
	bool check() { return ( (f[1].check()) | ( o >= 7 ) ) ; }
	bool operator < ( const mj &x ) const {
		if( x.f[0] != f[0] ) return ( f[0] < x.f[0] ) ;
		if( x.f[1] != f[1] ) return ( f[1] < x.f[1] ) ;
		if( o != x.o ) return o < x.o ; return 0 ; 
	}
} Po, g[M] ;
mj update( mj S, int num ) {
	mj res ; res.f[0] = update( S.f[0], num ), 
	res.f[1] = update( S.f[1], num ) ;
	if( num >= 2 ) res.f[1] = mx( res.f[1], update( S.f[0], num - 2 ) ) ;
	res.o = min( 7ll, S.o + ( num >= 2 ) ) ;
	return res ; 
} map<mj, int> Id ; 
void dfs( mj S ) {
	if( Id[S] || S.check() ) return ;
	Id[S] = ++ cnt, g[cnt] = S ; 
	rep( i, 0, 4 ) dfs( update( S, i ) ) ; 
}
int fpow( int x, int k ) {
	int ans = 1, base = x ; 
	while(k) {
		if( k & 1 ) ans = ans * base % P ; 
		base = base * base % P, k >>= 1 ;  
	} return ans ; 
}
signed main()
{
	Po.init(), Po.f[0].a[0][0] = 0, dfs(Po) ;
	n = gi() ; int x, y, maxn = 4 * n ; dp[0][0][1] = 1, c[0][0] = 1, vis[0][1] = 1 ; 
	rep( i, 1, 13 ) x = gi(), y = gi(), ++ w[x] ;
	rep( i, 1, 4 ) rep( j, 0, 4 ) c[i][j] = ( !j ) ? 1 : c[i - 1][j] + c[i - 1][j - 1] ;  
	fac[0] = 1 ; rep( i, 1, 405 ) fac[i] = fac[i - 1] * i % P ; 
	rep( i, 1, n ) {
		for( int j = 0; j <= 4 * i; ++ j ) rep( k, 1, cnt ) dp[i & 1][j][k] = 0 ; 
		rep( k, 1, cnt ) rep( l, w[i], 4 ) {
			mj S = update( g[k], l ) ; int r = Id[S], u = i & 1 ; 
			if( !r || !vis[i - 1][k] ) continue ; 
			for( int j = 0; j <= 4 * (i - 1); ++ j ) 
				dp[u][j + l][r] = ( dp[u][j + l][r] + c[4 - w[i]][4 - l] * dp[u ^ 1][j][k] ) % P, vis[i][r] = 1 ; 
		}
	} int div = 1 ; 
	for( re int i = 13; i <= n * 4; ++ i ) {
		int ans = 0 ; rep( k, 1, cnt ) ans = ( ans + dp[n & 1][i][k] ) % P ; 
		ans = ans * fac[i - 13] % P, ans = ans * fpow( div, P - 2 ) % P, Ans = ( Ans + ans ) % P ; 
		div = div * ( maxn - i ) % P ; 
	} 
	cout << Ans << endl ; 
	return 0 ;
}
posted @ 2020-09-12 12:20  Soulist  阅读(149)  评论(0编辑  收藏  举报