1 条题解

  • 0
    @ 2026-10-27 8:00:08

    思路与算法

    花色不影响牌型,先统计3、4、…、K、A、2、小王、大王的张数。2和双王不参加顺子。把出牌分为两类:单顺(至少5个连续点)、双顺(至少3个连续点)、三顺(至少2个连续点);以及图表列出的全部非顺子牌型。 任意一套最优出牌的各手可以交换顺序而不改变合法性,所以可把所有顺子移到前面打。对当前各点张数,枚举每一种顺子的起点、终点和重复次数1/2/3,扣除后递归;以完整张数编码记忆化。每个状态也尝试直接用最优非顺子方案清掉残牌。 非顺子与点数之间的大小、邻接无关,只需(h1,h2,h3,h4,j):有h_i个普通点恰剩i张,j个大小王。枚举每次从某类普通点取1、2、3、4张;3张可再带1张单牌或一个普通对子,4张可带任意两张单牌或任意两对普通对子,双王可以单独组成火箭。每次扣牌后相应修改直方图并记忆化。带牌允许拆开原有对子、三张、四张,不能只从恰好1张或2张的组中选。

    正确性说明

    一手牌型的合法性只取决于它包含的牌,因此不同出牌次序只要分配的牌不重叠就等价。每个合法分拆均可先打其中的全部顺子,其顺子在递归中必被枚举。剩余非顺子分拆正是尾部DP的目标。 对非顺子,任意交换两个普通点的剩余张数都不改变后续可选的非顺子组合,所以直方图是充分状态;大小王另记,是因为它们可一起构成火箭,却不是同点对子。尾部DP穷尽图表中的基本手型和合法带牌,并且每条转移都实际减少相应牌数,故可按剩余张数归纳得到最少手数。四带二的两对按图表“任意两对牌”理解,同一普通点有4张时可拆成两对;两王不能充当普通对子。三顺带单牌、三顺带对子不在题图规则中,程序不添加这些牌型。综合顺子枚举和尾部精确DP,所有合法分拆均有对应路径,每条路径也都是合法分拆,取最小值正确。

    复杂度

    令R为一手牌在扣除顺子后可达的完整张数状态数,H为总牌数不超过23的直方图状态数。完整状态每次枚举常数12个顺子点上的O(12²)区间并更新至多12张计数,尾部DP每状态枚举至多常数种组别和附带组合。用有序映射记忆化,总时间O(R·12³log R+H·4⁴),空间O(R+H)。初始化统计牌O(n),共享尾部数组初始化O(2^18),各测试组复用尾部结果。完整扣牌状态数不超过∏(a_i+1),15个点23张且王各至多1的范围内至多839808;实际只访问顺子可达子集。

    易错点

    A在顺子中接在K后面;2和王不能进入顺子。顺子最短长度分别5、3、2。不要照搬普通斗地主的飞机带翅膀规则。双王是火箭而非同点对子,但可分别当单牌。三带一与四带二都可拆已有组提供附带牌。

    参考代码(C++11)

    #include <bits/stdc++.h>
    using namespace std;
    int histogramMemo[1 << 18];
    int encode(const array<int, 5> &h, int j) {
        return h[1] | (h[2] << 4) | (h[3] << 8) | (h[4] << 12) | (j << 16);
    }
    vector<pair<array<int, 5>, int>> removeGroups(const array<int, 5> &h, int j, int amount) {
        vector<pair<array<int, 5>, int>> out;
        for (int size = amount; size <= 4; size++)
            if (h[size]) {
                auto z = h;
                z[size]--;
                if (size > amount)
                    z[size - amount]++;
                out.push_back({z, j});
            }
        if (amount == 1 && j)
            out.push_back({h, j - 1});
        return out;
    }
    int tailSolve(const array<int, 5> &h, int j) {
        int key = encode(h, j);
        int &memo = histogramMemo[key];
        if (memo >= 0)
            return memo;
        if (!key)
            return memo = 0;
        int best = h[1] + h[2] + h[3] + h[4] + (j ? 1 : 0);
        if (j == 2)
            best = min(best, 1 + tailSolve(h, 0));
        for (int core = 1; core <= 4; core++)
            for (auto base : removeGroups(h, j, core)) {
                best = min(best, 1 + tailSolve(base.first, base.second));
                if (core == 3) {
                    for (int amount = 1; amount <= 2; amount++)
                        for (auto z : removeGroups(base.first, base.second, amount))
                            best = min(best, 1 + tailSolve(z.first, z.second));
                }
                if (core == 4) {
                    for (int amount = 1; amount <= 2; amount++)
                        for (auto a : removeGroups(base.first, base.second, amount))
                            for (auto b : removeGroups(a.first, a.second, amount))
                                best = min(best, 1 + tailSolve(b.first, b.second));
                }
            }
        return memo = best;
    }
    struct Solver {
        array<int, 15> a;
        map<unsigned long long, int> memo;
        unsigned long long code() {
            unsigned long long k = 0;
            for (int i = 0; i < 15; i++)
                k |= (unsigned long long)a[i] << (3 * i);
            return k;
        }
        int solve() {
            auto key = code();
            auto old = memo.find(key);
            if (old != memo.end())
                return old->second;
            array<int, 5> h{};
            for (int i = 0; i < 13; i++)
                if (a[i])
                    h[a[i]]++;
            int best = tailSolve(h, a[13] + a[14]);
            if (best <= 1)
                return memo[key] = best;
            for (int count = 1; count <= 3; count++) {
                int minimum = (count == 1 ? 5 : count == 2 ? 3 : 2);
                for (int l = 0; l < 12; l++) {
                    int r = l;
                    while (r < 12 && a[r] >= count)
                        r++;
                    for (int end = l + minimum; end <= r; end++) {
                        for (int i = l; i < end; i++)
                            a[i] -= count;
                        best = min(best, 1 + solve());
                        for (int i = l; i < end; i++)
                            a[i] += count;
                    }
                }
            }
            return memo[key] = best;
        }
    };
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        fill(histogramMemo, histogramMemo + (1 << 18), -1);
        int T, n;
        cin >> T >> n;
        while (T--) {
            Solver s;
            s.a.fill(0);
            for (int i = 0, x, color; i < n; i++) {
                cin >> x >> color;
                int rank = x == 0 ? 12 + color : x == 1 ? 11 : x == 2 ? 12 : x - 3;
                s.a[rank]++;
            }
            cout << s.solve() << '\n';
        }
    }
    
    • 1

    信息

    ID
    2055
    时间
    2000ms
    内存
    512MiB
    难度
    提高
    标签
    递交数
    0
    已通过
    0
    上传者