1. 程式人生 > 實用技巧 >[ZJOI 2019] 麻將

[ZJOI 2019] 麻將

[題目連結]

https://loj.ac/problem/3042

[題解]

首先考慮將期望拆開 , 有 E(x) = sigma { P (x > i) }

我們需要求出i張牌仍不能胡牌的概率 , 顯然可以轉化為求方案數。

直接動態規劃是不好做的 , 但如果我們能將當前手上的麻將狀態壓成一個數 , 那麼就可以設 dp(i , j , k)表示前i種麻將 , 狀態為j , 一共選了k張牌的方案數。

如何將狀態壓縮呢?

考慮給定一手牌 , 如何判定它是否能胡牌?

我們發現對於每個i , 形如"i , i + 1 , i + 2"這樣的"順子"是不超過3個的 , 因為如果超過可以用形如"i , i , i"這樣型別的“面子”來替換。 那麼可以設f(i , j , k , l)表示前i種牌 , (i - 1)開頭的“順子”有j個 , i開頭的“順子”有k個 , 另有l ( l <= 1) 個對子。 這樣一共有O(18N)種狀態 , 直接轉移即可 , 具體細節不再贅述。

進一步觀察這個動態規劃 , 我們發現i其實是無關緊要的 , 這個過程的本質就是每次新加一種型別的麻將 ,更新一個3 * 3的矩陣。 不妨考慮建立有限狀態自動機(DFA) , 直接將這個3 * 3的矩陣做為狀態 , 將"胡"的節點做為終止節點。 暴力構建這個自動機 , 發現其狀態數很小 ,為3956

那麼我們就解決了狀態壓縮的問題。

回到剛才的思路 , 不妨設dp(i , j , k)表示加入了i種類型的麻將 , 現在在自動機上j號節點 , 一共選了k張牌的方案數。 用一些組合數學的技巧就可以實現轉移 , 具體細節不再贅述。

那麼這道題就做完了 , 時間複雜度 : O(N ^ 2M) (M為自動機的狀態數)

[程式碼]

/*
      Author : @evenbao
      Created : 2020 / 07 / 29 
*/

#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif

#include<bits/stdc++.h>

using namespace std;

typedef long long LL;

#define pii pair<int , int>
#define mp make_pair
#define fi first
#define se second

const int N = 1e2 + 5
; const int M = 4e3 + 5; const int mod = 998244353; template <typename T> inline void chkmax(T &x , T y) { x = max(x , y); } template <typename T> inline void chkmin(T &x , T y) { x = min(x , y); } template <typename T> inline void read(T &x) { T f = 1; x = 0; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + c - '0'; x *= f; } inline void inc(int &x , int y) { x = x + y < mod ? x + y : x + y - mod; } inline void dec(int &x , int y) { x = x - y >= 0 ? x - y : x - y + mod; } inline int quickpow(int a , int n) { int b = a , res = 1; for (; n; n >>= 1 , a = (LL) a * a % mod) if (n & 1) res = (LL) res * a % mod; return res; } struct State { int dp[3][3]; State() { memset(dp , 255 , sizeof(dp)); } friend bool operator < (State a , State b) { for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) if (a.dp[i][j] != b.dp[i][j]) return a.dp[i][j] < b.dp[i][j]; return false; } friend State Max(State a , State b) { State c; for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) c.dp[i][j] = max(a.dp[i][j] , b.dp[i][j]); return c; } friend State Trans(State a , int b) { State c; for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) if (~a.dp[i][j]) for (int k = 0; k < 3 && i + j + k <= b; ++k) chkmax(c.dp[j][k] , min(i + a.dp[i][j] + (b - i - j - k) / 3 , 4)); return c; } } ; struct Mahjong { pair < State , State > god; int cnt; Mahjong() { memset(god.first.dp , 255 , sizeof(god.first.dp)); memset(god.second.dp , 255 , sizeof(god.second.dp)); god.first.dp[0][0] = cnt = 0; } friend bool operator < (Mahjong a , Mahjong b) { return a.cnt != b.cnt ? a.cnt < b.cnt : a.god < b.god; } friend Mahjong Trans(Mahjong a , int b) { a.cnt = min(a.cnt + (b >= 2) , 7); a.god.second = Trans(a.god.second , b); if (b >= 2) a.god.second = Max(a.god.second , Trans(a.god.first , b - 2)); a.god.first = Trans(a.god.first , b); return a; } inline bool right() { if (cnt >= 7) return 1; for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) if (god.second.dp[i][j] == 4) return 1; return 0; } } mahjong[M]; int n , tot; map < Mahjong , int > idx; bool win[M]; int org[N] , dp[N][M][4 * N] , trans[M][5] , fac[M] , c[M][5]; inline void Dfs_Mahjong(Mahjong now) { if (idx.find(now) != idx.end()) return; mahjong[++tot] = now; win[tot] = now.right(); idx[now] = tot; for (int i = 0; i <= 4; ++i) Dfs_Mahjong(Trans(now , i)); } int main() { fac[0] = 1; for (int i = 1; i < M; ++i) fac[i] = (LL) fac[i - 1] * i % mod; for (int i = 0; i < M; ++i) { c[i][0] = 1; for (int j = 1; j <= min(i , 4); ++j) c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod; continue; } Dfs_Mahjong(Mahjong()); for (int i = 1; i <= tot; ++i) for (int j = 0; j <= 4; ++j) trans[i][j] = idx[Trans(mahjong[i] , j)]; scanf("%d" , &n); for (int i = 0; i < 13; ++i) { int x; scanf("%d%*d" , &x); ++org[x]; } dp[0][1][0] = 1; for (int i = 0 , cp = 0; i < n; ++i) { cp += org[i + 1]; for (int j = 1; j <= tot; ++j) { for (int l = org[i + 1]; l <= 4; ++l) { int *nf = dp[i + 1][trans[j][l]] , *ff = dp[i][j]; int tmp = (LL) c[4 - org[i + 1]][l - org[i + 1]] * fac[l - org[i + 1]] % mod; for (int k = 0; k + l <= 4 * n; ++k) { if (!ff[k]) continue; inc(nf[k + l] , (LL) ff[k] * tmp % mod * c[k + l - cp][l - org[i + 1]] % mod); } } } } int ans = 0 , dw = 1; for (int i = 13; i <= 4 * n; ++i) { int up = 0; for (int j = 1; j <= tot; ++j) if (!win[j]) inc(up , dp[n][j][i]); inc(ans , (LL) up * quickpow(dw , mod - 2) % mod); dw = (LL) dw * (4 * n - i) % mod; } printf("%d\n" , ans); return 0; }