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. 判断条件:单调递增 + 无缺失
根据你的提示,我们需要同时满足两个条件:
- 单调递增:所有成员的第一次出现位置必须严格递增。
- 无缺失:所有成员都必须出现在幻灯片中。
如何判断单调递增?
通过线段树查询根节点的状态:
- 如果
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} $ 后,我们需要:
- 修改对应成员的
set。 - 更新线段树中的信息。
- 重新判断是否满足条件。
代码实现
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. 总结代码逻辑
-
初始化:
- 构建线段树,记录每个成员的第一次出现位置。
- 使用
set记录成员的出现位置。 - 判断初始状态是否满足条件。
-
更新操作:
- 修改
set和线段树。 - 重新判断是否满足条件。
- 修改
-
输出结果:
- 如果满足条件,输出
YA;否则输出TIDAK。
- 如果满足条件,输出

浙公网安备 33010602011771号