Files
python/TangDou/JiShuanKe/2023-03-16/提高挑战题.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

14 KiB
Raw Blame History

mahjong

题目描述

ame 是一个可爱的女孩子,她喜欢打麻将。

ame 找来了一副特殊的麻将,为了防止你不知道麻将的相关规则,在此给出一些定义:

  • 这副麻将共含有 m 种花色,有 n 种不同的数字,大小分别为 1 到 $n$。
  • 一共有 n\times m 种牌,每种牌为一个两两不同的二元组 $(x,y)$,表示这种牌数字为 $x$,花色为 $y$,每种牌都有 4 张。其中 $1\leq x\leq n,1\leq y\leq m$。
  • 一组面子可以为一组刻子或一组顺子。
  • 定义一组顺子是三张花色相同、数字连续的牌。
  • 定义一组刻子是三张花色和数字组合完全相同的牌。
  • 定义一组对子是两张花色和数字组合完全相同的牌。

定义一个麻将牌可重集合 S 是胡的当且仅当:

  • 大小为 $3k+2$,其中 k 为给定的数值。
  • 能够被划分为 k 组面子和一组对子。

游戏开始时,ame 从所有牌当中取出了一个大小为 3k+2 的麻将牌集合 $S$,她想知道对于所有不同的 $S$,其中能胡的有多少个。

定义两个麻将牌集合 S 和 T 不同当且仅当存在一种牌,在 S 中的出现次数和在 T 中的出现次数不同。

对于所有数据,$4\leq n \leq 10^9$,$1\leq m\leq 10^9$,$1\leq k\leq 100$。

输入格式

输入共 1 行 3 个正整数 $n,m,k$。

输出格式

输出共 1 行 1 个整数,表示对 998244353 取模之后的答案。

数据范围

对于所有数据,$4\leq n \leq 10^9$,$1\leq m\leq 10^9$,$1\leq k\leq 100$。

数据编号 n \leq m k\leq 时间限制
1 9 = 1 4 4s
2,3,4,5,6 100 \leq 10^9 100 4s
7,8,9,10,11 10^9 =n 100 4s
12 10^9 \leq 10^9 1 4s
13 10^9 \leq 10^9 2 4s
14 10^9 \leq 10^9 3 4s
15 10^9 \leq 10^9 4 4s
16,17,18,19,20 10^9 \leq 10^9 30 4s
21,22,23,24,25 10^9 \leq 10^9 100 4s

题解

算法一

暴力枚举麻将牌集合,然后贪心判断即可。

算法二

可以手玩方案数!

算法三

显然能够发现如果设 f_{i,0/1} 表示合法麻将牌集合 S 的大小为 $i$,且这些牌的花色相同,其中包不包含一个对子的数量,则能用组合数 O\left(k^3\right) 求出答案。

具体而言,设 g_{i,j} 表示共有 i 种花色,集合大小为 j 的方案数。那么可以用 f 背包求出 g ,然后再用 g 组合数即可。

考虑如何求出 $f_{i,0/1}$,我们能够设一个 dp,然后从 1 到 n 依次考虑每个大小的牌加多少个,现在

需要判断的是加进去之后是否合法。

转化为一个这样的命题:给定一个集合,能否胡牌。有一个简单的 dp,设 dp_{i,s_1,s_2,0/1}=[0,1] 表示当前考虑第 i 张牌,从 i-2 开始的顺子有 s_1 个,从 i-1 开始的顺子有 s_2 个,有没有对子的情况是否合法。转移显然。

发现 (s_1,s_2,0/1) 的总状态数量为 $18$,也就是说,用一个大小为 18 的数组就能包括当前麻将牌集合能表示出的所有状态。

那么我们设 dp_{i,j,S} 为,当前加入到第 i 张牌,一共加入了 j 张牌,能表示状态的集合为 S 的方案数。转移显然。

不过 2^{18} 种方案显然是非常大的,我们能从初始状态预处理出能到达的所有状态,经打表一共只有 68 种。

68 种的预处理可以参考 ZJOI2019 麻将的 bfs 写法,接着套用上面那个 dp 就行了。

时间复杂度:$O(68nk)$。

算法四

经实践,当 k 固定时,能用 BM 算法暴力求出递推式,可以通过 k=1,2,3,4 的部分分。

算法五

k 较小的情况下,发现有大量的数字都不会被加入。

那么我们只需考虑若干连续的数字组成的方案数,然后再合并即可。

考虑每一个合法的仅有一种花色且集合大小为 s 的牌,显然 $s=3n+2(n\in \mathbb N)$,且能够将其分为若干段,这若干段每段的数字连续(可重复),但是不同段之间的数字不连续,显然对每段的牌内部也应是合法的。那么设 f_{i} 表示分出来的某一段长度为 i 的方案数,这个可以用算法三中的方法计算,然后再令 g_{i,j} 表示同一种花色共有 i 段,集合大小为 j 的方案数,这个可以用求得的 f 背包得到。

注意这个 g_{i,j} 不考虑具体的牌的大小,但是考虑不同段之间大小的相对顺序。

  • 例如对于牌组 \{2,3,4,6,7,8,9,9\} 在 g_{i,j} 中与 \{1,2,3,6,7,8,9,9\} 相同,但是与 \{2,3,4,5,5,7,8,9\} 不相同。

求得 g_{i,j} 后可以用组合数求出考虑具体牌的大小后的方案数,最后用求得的 g_{i,j} 再组合数背包求出考虑多个花色的答案。

时间复杂度:$O\left(68\times 9\times k^3\right)$。

算法六

我们发现被卡常了,想一下怎么优化常数。

对于每个状态 $S$,当前合法的麻将牌集合大小模 3 的余数一定相等。

发现常数主要集中在求 f 的地方,然后发现对预处理的 68 种状态,满足某个状态的牌,一定满足数量大小模 3 的余数大小一定。于是可以用这个性质优化常数,再优化一下循环的上下界即可。

使用类似思想可以优化 9 倍常数。

时间复杂度:$O\left(68k^3\right)$。

参考代码

#include <algorithm>
#include <iostream>
#include <iomanip>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <queue>
#include <ctime>
#include <map>
using namespace std;
#define mod 998244353
#define ll long long
int dp[2][2][305][70][305], n, m, k, K;
int g[305][305][305][2], h[305][305][2], f[305], inv[305];
int ans, nex, len[70];
struct data {
    int dp[3][3];
    data() {
        memset(dp, 0, sizeof(dp));
    }
    friend bool operator <(const data a, const data b) {
        for (int i = 0; i <= 2; i++) {
            for (int t = 0; t <= 2; t++) {
                if (a.dp[i][t] != b.dp[i][t]) {
                    return a.dp[i][t] < b.dp[i][t];
                }
            }
        }
        return 0;
    }
    friend bool operator !=(const data a, const data b) {
        for (int i = 0; i <= 2; i++) {
            for (int t = 0; t <= 2; t++) {
                if (a.dp[i][t] != b.dp[i][t]) {
                    return 1;
                }
            }
        }
        return 0;
    }
    void DP(data& c, int del) {
        for (int i = 0; i <= 2; i++) {
            for (int t = 0; t <= 2; t++) {
                if (dp[i][t]) {
                    if (del >= i + t) {
                        c.dp[t][(del - i - t) % 3] |= 1;
                    }
                }
            }
        }
    }
};
struct node {
    data is_pair[2];
    void clear() {
        is_pair[0] = data();
        is_pair[1] = data();
    }
    friend bool operator <(const node a, const node b) {
        if (a.is_pair[0] != b.is_pair[0]) {
            return a.is_pair[0] < b.is_pair[0];
        }
        if (a.is_pair[1] != b.is_pair[1]) {
            return a.is_pair[1] < b.is_pair[1];
        }
        return 0;
    }
    friend node operator +(node a, int b) {
        node c;
        if (b >= 2) {
            a.is_pair[0].DP(c.is_pair[1], b - 2);
        }
        a.is_pair[0].DP(c.is_pair[0], b);
        a.is_pair[1].DP(c.is_pair[1], b);
        return c;
    }
} st, sav[205];
map<node, int> mp;
int tot, c[200005][25];
void bfs() {
    queue<node> q;
    st.clear();
    st.is_pair[0].dp[0][0] = 1;
    q.push(st);
    mp[st] = ++tot;
    sav[tot] = st;
    len[tot] = 0;
    while (!q.empty()) {
        node x = q.front();
        q.pop();
        int now = mp[x];
        for (int i = 1; i <= 4; i++) {
            node nex = x + i;
            if (mp[nex]) {
                c[now][i] = mp[nex];
            } else {
                mp[nex] = ++tot;
                c[now][i] = tot;
                q.push(nex);
                sav[tot] = nex;
                len[tot] = (len[now] + i) % 3;
            }
        }
    }
}
int add(int x) {
    return x >= mod ? x - mod : x;
}
int ksm(int x, int y) {
    int ans = 1, t = x;
    while (y) {
        if (y& 1) {
            ans = 1ll * ans * t % mod;
        }
        t = 1ll * t * t % mod;
        y >>= 1;
    }
    return ans;
}
int C(int n, int m) {
    if (n < m || m < 0) {
        return 0;
    }
    return 1ll * f[n] * inv[n - m] % mod * inv[m] % mod;
}
int main() {
    freopen("mahjong.in", "r", stdin);
    freopen("mahjong.out", "w", stdout);
    bfs();
    dp[0][0][0][1][0] = g[0][0][0][0] = 1;
    scanf("%d%d%d", &n, &m, &k);
    K = 3 * k + 2;
    for (int s = 0; s <= k; s++) {
        nex ^= 1;
        for (int i = 0; i <= K; i++) {
            int qwq = min(4 * i, K);
            for (int j = 1; j <= tot; j++) {
                for (int t = i - i % 3 + len[j]; t <= qwq; t += 3) {
                    dp[nex][0][i][j][t] = 0;
                }
            }
            for (int j = 1; j <= tot; j++) {
                for (int t = i - i % 3 + (len[j] + 2) % 3; t <= qwq; t += 3) {
                    dp[nex][1][i][j][t] = 0;
                }
            }
        }
        for (int i = 0; i <= K; i++) {
            int qwq = min(4 * i, K);
            for (int t = 0; t <= qwq; t++) {
                g[s + 1][i][t][0] = mod - dp[nex ^ 1][0][i][1][t];
                g[s + 1][i][t][1] = mod - dp[nex ^ 1][1][i][1][t];
            }
        }
        for (int i = 0; i < K; i++) {
            int qwq = min(4 * i, K - 1);
            for (int j = 1; j <= tot; j++) {
                for (int t = i - i % 3 + len[j]; t <= qwq; t += 3) {
                    if (!dp[nex ^ 1][0][i][j][t]) {
                        continue;
                    }
                    dp[nex^1][0][i+1][c[j][1]][t+1] = add(dp[nex^1][0][i+1][c[j][1]][t+1] 
                    + dp[nex^1][0][i][j][t]);
                    dp[nex^1][0][i+1][c[j][2]][t+2] = add(dp[nex^1][0][i+1][c[j][2]][t+2] 
                    + dp[nex^1][0][i][j][t]);
                    dp[nex^1][0][i+1][c[j][3]][t+3] = add(dp[nex^1][0][i+1][c[j][3]][t+3] 
                    + dp[nex^1][0][i][j][t]);
                    dp[nex^1][0][i+1][c[j][4]][t+4] = add(dp[nex^1][0][i+1][c[j][4]][t+4] 
                    + dp[nex^1][0][i][j][t]);
                }
            }
        }
        for (int i = 0; i < K; i++) {
            int qwq = min(4 * i, K - 1);
            for (int j = 1; j <= tot; j++) {
                for (int t = i - i % 3 + (len[j] + 2) % 3; t <= qwq; t += 3) {
                    if (!dp[nex ^ 1][1][i][j][t]) {
                        continue;
                    }
                    dp[nex^1][1][i+1][c[j][1]][t+1] = add(dp[nex^1][1][i+1][c[j][1]][t+1] 
                    + dp[nex^1][1][i][j][t]);
                    dp[nex^1][1][i+1][c[j][2]][t+2] = add(dp[nex^1][1][i+1][c[j][2]][t+2] 
                    + dp[nex^1][1][i][j][t]);
                    dp[nex^1][1][i+1][c[j][3]][t+3] = add(dp[nex^1][1][i+1][c[j][3]][t+3] 
                    + dp[nex^1][1][i][j][t]);
                    dp[nex^1][1][i+1][c[j][4]][t+4] = add(dp[nex^1][1][i+1][c[j][4]][t+4] 
                    + dp[nex^1][1][i][j][t]);
                }
            }
        }
        for (int j = 1; j <= tot; j++) {
            if (sav[j].is_pair[0].dp[0][0]) {
                for (int i = 0; i <= K; i++) {
                    int qwq = min(4 * i, K);
                    for (int t = i - i % 3; t <= qwq; t += 3) {
                        dp[nex][0][i][1][t]=add(dp[nex][0][i][1][t]+dp[nex^1][0][i][j][t]);
                    }
                    for (int t = i - i % 3 + 2; t <= qwq; t += 3) {
                        dp[nex][1][i][1][t]=add(dp[nex][1][i][1][t]+dp[nex^1][1][i][j][t]);
                    }
                }
            }
            if (sav[j].is_pair[1].dp[0][0]) {
                for (int i = 0; i <= K; i++) {
                    int qwq = min(4 * i, K);
                    for (int t = i - i % 3 + 2; t <= qwq; t += 3) {
                        dp[nex][1][i][1][t]=add(dp[nex][1][i][1][t]+dp[nex^1][0][i][j][t]);
                    }
                }
            }
        }
        for (int i = 0; i <= K; i++) {
            int qwq = min(4 * i, K);
            for (int t = 0; t <= qwq; t++) {
                g[s+1][i][t][0]=dp[nex][0][i][1][t]=add(g[s+1][i][t][0]+dp[nex][0][i][1][t]);
                g[s+1][i][t][1]=dp[nex][1][i][1][t]=add(g[s+1][i][t][1]+dp[nex][1][i][1][t]);
            }
        }
    }
    f[0] = 1;
    for (int i = 1; i <= K; i++) {
        f[i] = 1ll * f[i - 1] * i % mod;
    }
    inv[K] = ksm(f[K], mod - 2);
    for (int i = K - 1; i >= 0; i--) {
        inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
    }
    for (int i = 0; i <= K; i++) {
        int now = n + 1 - i;
        if (now < 0) {
            break;
        }
        for (int s = 1,w = now,res = now - 1; s <= k + 1; s++, w = 1ll * w * res % mod, res--) {
            for (int t = 0; t <= K; t++) {
                for (int q = 0; q <= 1; q++) {
                    h[1][t][q] = (h[1][t][q] + 1ll * g[s][i][t][q] * w % mod * inv[s]) % mod;
                }
            }
        }
    }
    h[1][0][0] = 0;
    for (int s = 1; s <= k; s++) {
        for (int i = 0; i <= K; i++) {
            for (int q = 0; q <= 1; q++) {
                for (int j = 0; i + j <= K; j++) {
                    h[s+1][i+j][q]=(h[s+1][i+j][q]+1ll*h[s][i][q]*h[1][j][0])%mod;
                }
            }
            for (int j = 0; i + j <= K; j++) {
                h[s + 1][i + j][1] = (h[s + 1][i + j][1] + 1ll * h[s][i][0] * h[1][j][1]) % mod;
            }
        }
    }
    for (int s = 1, now = m, res = m - 1; s <= k + 1; s++, now = 1ll * now * res % mod, res--) {
        ans = (ans + 1ll * h[s][K][1] * now % mod * inv[s]) % mod;
    }
    printf("%d", ans);
}