NOI 2025 见!

 

题解:CF1413D Shurikens

题目链接

若顺序、在线地处理每个事件,实时维护柜台中的剑的价格组成的集合 \(A\)

  • 对于 + 事件:指定一个尚未被指定过的价格 \(v \in [n]\),将 \(v\) 加入 \(A\)
  • 对于 - x 事件:若满足 \(\min(A) = x\),则从 \(A\) 中删除 \(x\),报告成功;否则报告失败。

目标是对于每个 + 事件指定合适的 \(v\),使得每个 - x 事件都成功。

这是困难的——一个 +\(v\) 的取值往往取决于后发生的 - x 事件的 \(x\) 的取值。而若从一个 - x 事件的角度考虑,则在此事件发生之前,发生过这样的 + 事件是必要的:

  • + 事件的 \(v\) 被指定了 \(x\)

我们称这样的 + 事件与此 - x 事件的关系是互相“配对“的。在题目的限制下,能与一个事件配对的事件是唯一的,且最多只可能有一个配对的事件;目标是使得每个事件都恰好有一个配对的事件。

既然能与 - x 事件配对的 + 事件总是在 - x 发生前,考虑倒序处理每个事件,并实时维护一个集合 \(S\)。假设当前处理到倒数第 \(i\) 个事件,则 \(S\) 存放着:所有尚未找到与其配对的 + 事件的 - x 事件的所有的 \(x\)

用一段伪代码详细讲解如何维护:


倒序遍历每个事件:

  • 若当前事件为 +

    • \(S\) 为空
      • 报告无解

        因为不存在可与其配对的 - x 事件了。

    • 否则
      • \(v\) 指定为 \(\min(S)\),并从 \(S\) 中删除 \(v\)

        首先总要选一个后发生的 - x 事件配对,而且选哪个都是能配对的,直接把 \(v\) 设置成想配对的 - x\(x\) 即可。但选其中 \(x\) 最小的,是最“优”的。这里的“优”定义为,若这样做不可行,没有任何一种其他方法是可行的。

        注意在处理 - x 时,判断是否无解的依据是 \(\min(S)\) 是否小于 \(x\)

        若删除的是 \(\min(S)\),那么删除后新的 \(\min(S)\) 必然变大;对于固定的 \(x\),要么使得 \([\min(S) < x]\) 的结果不变,要么使得 \([\min(S) < x]\)\(0\) 变为 \(1\)

        否则 \([\min(S) < x]\) 一定不变。

        而且对任何时刻下的 \(S\) 都是如此,不受做过的决策影响。因此删除 \(\min(S)\) 一定是最“优”的。

  • 否则当前事件为 - x

    • \(S\) 不为空且 \(\min(S) < x\)
      • 报告无解

        意味着在当前事件发生后,还发生过一个仍未找到与其配对的 + 事件的 - x 事件,且其 \(x\),即能与其配对的 + 事件的 \(v\),是 \(\min(S)\),小于当前事件的 \(x\)

        这会导致,若在当前事件发生前的一个 + 事件的 \(v\) 被指定了 \(\min(S)\),会使得当前事件的 \(x\) 必定无法成为 \(\min(S)\),无法被成功删除;而在当前事件发生后,又没有 + 事件能与其配对。因此矛盾,必定无解。

    • 否则
      • \(x\) 加入 \(S\)

插入、查询最小值、删除最小值——可以使用堆维护 \(S\) 做到 \(O(n \log n)\)

注意到若有解,每次处理 - x 事件时,只需要做一件事:将 \(x\) 加入 \(S\);而此时必有 \(x < \min(S)\)。因此只需要维护一个栈,要加入一个数时直接将其压入作为栈顶,就可以保证栈是从栈底至栈顶单调递减的了。查询最小值,即查询栈顶;删除最小值,即删除栈顶。

时空复杂度 \(\Theta(n)\) 的参考实现:

#include <bits/stdc++.h>
using namespace std;

constexpr int N = 100000 + 1;

char op[N + N];
int x[N + N], stk[N], ans[N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;
    for (int i = 1; i <= n + n; ++i) {
        cin >> op[i];
        if (op[i] == '-') {
            cin >> x[i];
        }
    }
    int p = n, top = 0;
    for (int i = n + n; i; --i) {
        if (op[i] == '+') {
            if (!top) {
                cout << "NO\n";
                return 0;
            }
            ans[p--] = stk[top--];
        } else {
            if (top && stk[top] < x[i]) {
                cout << "NO\n";
                return 0;
            }
            stk[++top] = x[i];
        }
    }
    cout << "YES\n";
    for (int i = 1; i <= n; ++i) {
        cout << ans[i] << ' ';
    }
    cout << '\n';
    return 0;
}

posted on 2026-01-06 16:35  SkyWave2022  阅读(16)  评论(0)    收藏  举报

导航