ZJOI2019 麻将
给定 (nge 5) 表示有 (4cdot n) 张牌,然后给定 (13) 张基础牌,剩余的 (4n-13) 张牌随机打乱,求期望至少拿走多少张牌后可以胡牌。
胡牌当且仅当当前牌集存在一个大小为 (14) 的子集,满足可以拆分为:
-
一个对子和 (4) 个面子
-
(7) 个大小不同的对子。
-
对子即两张大小相同的牌,面子即三张大小连续的牌,或大小相同的牌。
-
(nle 100)
( m Sol:)
先考虑一个子问题,我们已知一副手牌,希望知道他是否满足存在子集是胡牌。
我们发现第二个条件非常好判断,所以实际上我们只关注第一个条件,我们考虑通过一个 dp 来检查一副牌是否满足第一个条件。
设 (f_{i,0/1}) 表示当前 dp 到位置 (i),前面是否存在对子能得到的最大面子数。
我们发现对于加入 (i) 之后并进行 check,我们还需要知道 (i-1,i-2) 的剩余数量,显然我们不关注更远的位置,同时注意到顺子至多跑 (2) 个,三个会被认为是三个面子,所以还需要加两个维度来检查即可,每个维度的取值范围为 (0sim 2),这样的话到一个确定的位置,一个确定的状态就能且仅能被 (3cdot 3) 大小的矩阵描述了。
不过更方便的处理是设 (f_{i,0/1,j,k}) 表示预留了 (j) 对 ((i-1,i)),和 (k) 个 (i) 时最大的面子数。
哦,你以为是 (3cdot 3),其实是 (2cdot 3cdot 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(2091cdot n^2cdot 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 ;
}