[Luogu 1820] 麻将 加强加强版

观察:三组相同的顺子可以变成三组刻子,因此不妨令每种顺子的数量小于 \(3\)

记编号为 \(i\) 的牌有 \(a_i\) 张。考虑确定 \(a\) 后,判定是否和牌。

\(f(x, p, q, 0/1)\) 表示用完编号为 \(1 \sim x\) 的牌,有 \(p\)\((x - 1, x, x + 1)\) 的顺子,\(q\)\((x, x + 1, x + 2)\) 的顺子,是否使用雀头,的情况是否可能。其中 \(p, q \in \{0, 1, 2\}\)

转移时讨论是否在当前位置使用雀头:

  • 不使用,\(f(x - 1, p, q, 0/1) \rightarrow f(x, q, (a_x - p - q) \bmod 3, 0/1)\)
  • 使用,\(f(x - 1, p, q, 0) \rightarrow f(x, q, (a_x - p - q - 2) \bmod 3, 1)\)

暴力枚举在哪里 \(+1\) 可得到 \(O(n^2)\) 算法。

在此基础上,有三种线性做法。

  1. 在原来的 DP 的基础上新增一维表示是否已经听牌,记录转移并建图倒推合法听牌位置,常数较大。具体可参考:麻将 加强加强版 题解 by Milkcat

  2. 尝试在序列上动态 DP,预处理每个位置上的转移矩阵,矩阵乘法预处理前缀后缀,压位后仍有 \(18^2\) 倍常数。(口胡,可能可以进一步优化?

  3. 考虑正着倒着分别 DP 一遍,然后假定在 \(x\) 处听牌,枚举使用了 \(p\)\((x - 2, x - 1, x)\)\(q\)\((x, x + 1, x + 2)\),讨论雀头位于 \([1, x-1]\)\(x\)\([x+1, n]\) 中哪一部分,逐一判断能否成立。常数较小。

int n, m, a[MAXN];
bool f[MAXN][3][3][2], g[MAXN][3][3][2];

int main() {
    read(n, m);
    for (int i = 1, x; i <= m; ++i) read(x), ++a[x];
    f[0][0][0][0] = 1;
    for (int x = 1; x <= n; ++x) {
        for (int p : {0, 1, 2}) for (int q : {0, 1, 2}) for (int v : {0, 1}) {
            if (!f[x-1][p][q][v]) continue;
            if (a[x] >= p + q) f[x][q][(a[x]-p-q)%3][v] = 1;
            if (!v && a[x] >= p + q + 2) f[x][q][(a[x]-p-q-2)%3][1] = 1;
        }
    }
    g[n+1][0][0][0] = 1;
    for (int x = n; x; --x) {
        for (int p : {0, 1, 2}) for (int q : {0, 1, 2}) for (int v : {0, 1}) {
            if (!g[x+1][p][q][v]) continue;
            if (a[x] >= p + q) g[x][q][(a[x]-p-q)%3][v] = 1;
            if (!v && a[x] >= p + q + 2) g[x][q][(a[x]-p-q-2)%3][1] = 1;
        }
    }
    int cnt = 0;
    for (int x = 1; x <= n; ++x) {
        bool fl = 0;
        for (int p : {0, 1, 2}) for (int q : {0, 1, 2}) {
            int t0 = a[x] + 1 - p - q, t1 = a[x] - 1 - p - q;
            if (t0 >= 0) fl |= f[x-1][p][t0%3][0] && g[x+1][q][t0%3][1] 
                    || f[x-1][p][t0%3][1] && g[x+1][q][t0%3][0];
            if (t1 >= 0) fl |= f[x-1][p][t1%3][0] && g[x+1][q][t1%3][0];
            if (fl) break;
        }
        if (fl) write(x, ' '), ++cnt; 
    }
    if (!cnt) puts("QAQ");
    return 0;
}
posted @ 2025-08-05 14:54  SZwinsun  阅读(19)  评论(0)    收藏  举报