1 条题解

  • 0
    @ 2026-10-27 6:55:30

    思路与算法

    预处理每张牌同色下一次出现位置。每种当前未配对颜色只保留一张,记录所在栈。通常每栈至多两张,并留一个空栈;维护空栈集合、单牌栈按下一次出现排序的集合,以及“底牌先于顶牌再次出现”的危险双牌栈。 若新牌颜色已经存在:在顶部则直接放入自动消除;在底部则把新牌放保留空栈,再执行两栈底部消除。若是新颜色,优先填单牌栈或使用多余空栈。恰一空栈且其余全双牌时,若存在危险双牌栈,把新牌叠其上成为唯一三牌栈,空栈仍留;否则把新牌放最后空栈,进入无空栈状态。 无空栈时遇新颜色,选择下一次出现最晚的单牌栈叠放。由于k≤2n-1,新颜色到来时至少有两根单牌栈。每次集合状态随栈修改同步删除和重插;消除单牌栈后重新获得空栈。输出每次放牌与必要的底部消除操作。

    正确性说明

    保留空栈时底部重复能直接配对。唯一三牌栈由危险双牌栈叠加而来,其中间牌原顶牌的下一次出现晚于底牌,因此中间牌不会首先回来;先匹配顶部或底部即可把栈恢复到至多两张。 进入无空栈时所有双牌都安全,即顶牌先回来。之后对单牌栈叠放若产生危险双牌,选的是下一次出现最晚的单牌,故仍存在更早返回的单牌栈。保持不变量:所有危险双牌底牌的返回均晚于某个单牌的返回。因此在无空栈时期,下一次匹配不会需要取底部;若该更早单牌先配对,就已腾出空栈。新颜色时若只有一单牌与n-1双牌,则已保留2n-1种颜色,不能再有新颜色,故选择总可行。归纳每步能合法处理所有牌,偶数出现次数保证最后全部消除。每张输入牌一次放入,每次额外底部消除配掉两牌,故总操作≤m+m/2≤2m。

    复杂度

    预处理下一次出现O(m+k),集合初始化O(n),每张牌常数次有序集合操作O(log n),总O(m log n+n+k),空间O(m+n+k),其中操作输出与next数组均O(m)。所有测试组总牌数≤2000000。

    易错点

    栈顶自动消除与两栈底部消除不同。特判允许不同栈但底色不同时的无效操作,不错误拒绝题意允许的无效果步骤;同栈编号不合法。代码真实换行,正常构造不依赖无效操作。满规模输出超过12MiB时用同一沙箱缓存文件读取,保持CPU/内存约束。

    参考代码(C++11)

    #include <bits/stdc++.h>
    using namespace std;
    struct Op {
        int a, b, c;
    };
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int T;
        cin >> T;
        while (T--) {
            int n, m, k;
            cin >> n >> m >> k;
            vector<int> a(m), next(m), last(k + 1, m), expected(k + 1, m), owner(k + 1);
            for (int &x : a)
                cin >> x;
            for (int i = m - 1; i >= 0; i--) {
                next[i] = last[a[i]];
                last[a[i]] = i;
            }
            vector<deque<int>> s(n + 1);
            set<int> empty, unsafe;
            set<pair<int, int>> single;
            for (int u = 1; u <= n; u++)
                empty.insert(u);
            vector<Op> ops;
            ops.reserve(m + m / 2);
            auto eraseState = [&](int u) {
                if (s[u].empty())
                    empty.erase(u);
                else if (s[u].size() == 1)
                    single.erase({expected[s[u][0]], u});
                else if (s[u].size() == 2)
                    unsafe.erase(u);
            };
            auto addState = [&](int u) {
                if (s[u].empty())
                    empty.insert(u);
                else if (s[u].size() == 1)
                    single.insert({expected[s[u][0]], u});
                else if (s[u].size() == 2 && expected[s[u][0]] < expected[s[u][1]])
                    unsafe.insert(u);
            };
            for (int i = 0; i < m; i++) {
                int x = a[i], u = owner[x];
                if (u) {
                    eraseState(u);
                    if (s[u].back() == x) {
                        ops.push_back({1, u, 0});
                        s[u].pop_back();
                        owner[x] = 0;
                    } else {
                        assert(s[u].front() == x && !empty.empty());
                        int e = *empty.begin();
                        eraseState(e);
                        ops.push_back({1, e, 0});
                        ops.push_back({2, u, e});
                        s[u].pop_front();
                        owner[x] = 0;
                        addState(e);
                    }
                    addState(u);
                } else {
                    expected[x] = next[i];
                    assert(next[i] < m);
                    if (empty.empty()) {
                        assert(single.size() >= 2);
                        u = single.rbegin()->second;
                    } else if (!single.empty())
                        u = single.begin()->second;
                    else if (empty.size() >= 2)
                        u = *empty.begin();
                    else if (!unsafe.empty())
                        u = *unsafe.begin();
                    else
                        u = *empty.begin();
                    eraseState(u);
                    ops.push_back({1, u, 0});
                    s[u].push_back(x);
                    owner[x] = u;
                    assert(s[u].size() <= 3);
                    addState(u);
                }
            }
            assert(empty.size() == (size_t)n);
            assert(ops.size() >= (size_t)m && ops.size() <= (size_t)2 * m);
            cout << ops.size() << '\n';
            for (auto o : ops) {
                cout << o.a << ' ' << o.b;
                if (o.a == 2)
                    cout << ' ' << o.c;
                cout << '\n';
            }
        }
    }
    
    • 1

    信息

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