C2. Adjust The Presentation (Hard Version)线段树解法

1. 使用线段树维护成员的第一次出现位置

我们使用线段树来维护每个成员 $ a[i] $ 在幻灯片中的第一次出现位置,并确保这些位置按照顺序单调递增。这是判断演示文稿是否“好的”的关键。

线段树的作用

  • 每个节点维护一个区间 \([l, r]\),表示成员 $ a[l] $ 到 $ a[r] $ 的信息。
  • 节点中存储以下信息:
    • mx:该区间内所有成员的最大第一次出现位置
    • mi:该区间内所有成员的最小第一次出现位置
    • is_true:该区间内的成员是否满足单调递增条件。
    • is_null:该区间是否为空(即没有成员在幻灯片中出现)。

为什么需要单调递增?

幻灯片展示顺序是从第 $ 1 $ 张到第 $ m $ 张,因此如果某个成员 $ b_i $ 的第一次出现位置小于其前一个成员 $ b_{i-1} $ 的第一次出现位置,则无法满足题目要求。例如:

  • 如果 $ b_1 $ 的第一次出现位置是 $ 3 $,而 $ b_2 $ 的第一次出现位置是 $ 2 $,则 $ b_2 $ 不可能在 $ b_1 $ 后展示。

代码实现

void pushup(Tree& root, Tree& left, Tree& right) {
    if (right.is_null && left.is_null) {
        root.is_null = 1; // 左右子树都为空,则当前节点也为空
    } else if (right.is_null) {
        root.mx = left.mx;
        root.mi = left.mi;
        root.is_true = left.is_true;
        root.is_null = 0;
    } else if (left.is_null) {
        root.mx = right.mx;
        root.mi = right.mi;
        root.is_true = right.is_true;
        root.is_null = 0;
    } else {
        root.mx = max(left.mx, right.mx);
        root.mi = min(left.mi, right.mi);
        root.is_true = (left.is_true && right.is_true) && (left.mx < right.mi);
        root.is_null = 0;
    }
}
  • pushup 函数通过递归合并左右子树的信息,更新当前节点的状态。
  • 特别注意条件 (left.mx < right.mi):这确保了左子树的最大值小于右子树的最小值,从而保证整个区间的单调递增性。

2. 使用 set 维护成员的出现位置

为了高效地处理成员在幻灯片中的出现位置,我们为每个成员 $ a[i] $ 维护一个 set,记录其在幻灯片中的所有出现位置。

为什么需要 set

  • set 是有序集合,可以快速获取成员的第一次出现位置*s[a[i]].begin())。
  • 在更新操作中,当我们修改 $ b_{s_i} $ 时,可以通过 set 快速插入或删除对应的位置。

代码实现

set<int> s[N]; // 每个成员的出现位置
for (int i = 1; i <= m; i++) {
    cin >> b[i];
    s[b[i]].insert(i); // 将幻灯片编号 i 插入到成员 b[i] 的 set 中
}
  • 初始化时,将每张幻灯片的编号插入到对应成员的 set 中。
  • 更新操作时,删除旧的幻灯片编号,并插入新的幻灯片编号:
s[b[t]].erase(t); // 删除旧的幻灯片编号
s[p].insert(t);   // 插入新的幻灯片编号

3. 判断条件:单调递增 + 无缺失

根据你的提示,我们需要同时满足两个条件:

  1. 单调递增:所有成员的第一次出现位置必须严格递增。
  2. 无缺失:所有成员都必须出现在幻灯片中。

如何判断单调递增?

通过线段树查询根节点的状态:

  • 如果 tr[1].is_true == true,说明所有成员的第一次出现位置满足单调递增。

如何判断无缺失?

使用一个额外的 set 记录所有成员在数组 $ a $ 中的位置:

set<int> mx;
for (int i = 1; i <= m; i++) {
    mx.insert(mp[b[i]]); // 记录成员 b[i] 在 a 中的位置
}
bool ok = (mx.size() == *mx.rbegin());
  • mx.size() 表示当前幻灯片中涉及的成员数量。
  • *mx.rbegin() 表示最大成员编号。
  • 如果两者相等,则说明所有成员都出现了。

4. 更新操作

每次更新 $ b_{s_i} $ 后,我们需要:

  1. 修改对应成员的 set
  2. 更新线段树中的信息。
  3. 重新判断是否满足条件。

代码实现

while (q--) {
    int t, p;
    cin >> t >> p;
    s[b[t]].erase(t); // 删除旧的幻灯片编号
    update(1, mp[b[t]]); // 更新线段树
    if (s[b[t]].empty()) mx.erase(mp[b[t]]); // 如果成员不再出现,则从 mx 中删除
    b[t] = p; // 更新 b[t]
    s[b[t]].insert(t); // 插入新的幻灯片编号
    mx.insert(mp[b[t]]); // 更新 mx
    update(1, mp[b[t]]); // 再次更新线段树
    bool ok = (mx.size() == *mx.rbegin()); // 判断无缺失
    answer(); // 输出结果
}
  • 每次更新后,调用 update 函数更新线段树。
  • 通过 answer 函数输出当前状态是否满足条件。

5. 总结代码逻辑

  1. 初始化

    • 构建线段树,记录每个成员的第一次出现位置。
    • 使用 set 记录成员的出现位置。
    • 判断初始状态是否满足条件。
  2. 更新操作

    • 修改 set 和线段树。
    • 重新判断是否满足条件。
  3. 输出结果

    • 如果满足条件,输出 YA;否则输出 TIDAK
posted @ 2025-04-02 18:23  archer2333  阅读(42)  评论(0)    收藏  举报