zoukankan      html  css  js  c++  java
  • ZJOI2019 麻将

    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 ;
    }
    
  • 相关阅读:
    group by 详解
    NHibernte教程(10)--关联查询
    NHibernate教程(9)一1对n关联映射
    NHibernate教程(8)--巧用组件
    NHibernate教程(7)--并发控制
    Git初步配置 ubuntu服务器 windows客户端 虚拟机
    设计模式之GOF23状态模式
    设计模式之GOF23模板模式
    设计模式之GOF23策略
    设计模式之GOF23访问者模式
  • 原文地址:https://www.cnblogs.com/Soulist/p/13656535.html
Copyright © 2011-2022 走看看