1 条题解
-
0
题目解析
问题重述
在一棵 个节点的树上,Marat 从节点 出发要去节点 ,有 个敌人,每个敌人 从 走到 (最短路径)。敌人在时刻 从 出发,时刻 位于路径上的第 个节点。Marat 也在时刻 从 出发,每时刻可以移动到一个相邻节点或停留。Marat 不能在任何时刻与任何敌人在同一顶点相遇(可以在边上相遇)。敌人到达 后就不再存在。求 Marat 到达 的最早时刻,或输出 。
关键观察
1. 答案上界
如果答案不是 ,则答案 。
证明:设最优路径为 。在时刻 ,所有敌人都已到达工作地点(因为敌人路径长度 )。从 到 最多需要 步,所以 。
2. 坏时刻的定义
称 为坏的,如果在时刻 有敌人在顶点 。
性质:所有坏时刻的总数 $\le \sum_{i=1}^m \text{len}(path(a_i, b_i)) \le n \times m$。
因为每个敌人的路径长度 ,共有 个敌人。
动态规划思路
朴素 DP
定义 表示 Marat 能否在时刻 位于顶点 。
- 初始:
- 转移:如果 ,则对于 的所有邻居 以及 本身,
- 如果 是坏时刻,则
答案是最小的 使得 。
优化到
核心思想
维护当前时刻 可到达的顶点集合 ,而不是整个 表。
更新规则
从 到 :
- 坏时刻顶点:如果 是坏时刻,则
- 继承:如果 ,则 (可以停留)
- 邻居传播:如果 的某个邻居 ,则 (可以从 走过来)
两类特殊顶点
- 类型1:在时刻 有敌人的顶点(坏时刻顶点)
- 类型2:不在 中,但有邻居在 中的顶点
算法实现细节
数据结构
ok[v]:标记顶点 是否在当前集合 中was[v]:记录顶点 最后一次被加入集合的时刻(用于去重)cur[v]:标记在下一时刻是否有敌人到达 (临时标记)
处理流程
对于时刻 :
// 1. 标记当前时刻的敌人位置 if (t < block.size()) { for (auto &u : block[t]) cur[u] = true; } // 2. 找出下一时刻能到达的顶点 vi nxt; for (auto &v : q) { // q 是候选队列 if (ok[v] || cur[v] || was[v] == t) continue; was[v] = t; bool nei = 0; for (auto &u : g[v]) nei |= ok[u]; // 检查是否有邻居在当前集合 if (t && !nei) continue; // 时刻 0 不需要邻居(起点特殊情况) nxt.push_back(v); } // 3. 移除当前时刻有敌人的顶点 q.clear(); if (t < block.size()) { for (auto &u : block[t]) { cur[u] = false; if (ok[u]) ok[u] = false; q.push_back(u); // 这些顶点作为下一时刻的候选 } } // 4. 添加新到达的顶点 for (auto &v : nxt) { ok[v] = true; for (auto &u : g[v]) { if (!ok[u]) q.push_back(u); // 邻居作为候选 } } // 5. 检查答案 if (ok[y]) { cout << t + 1 << '\n'; return; } // 6. 终止条件 if (t > (int)block.size() && q.empty()) { cout << "-1\n"; return; }复杂度分析
每个顶点最多可以进入集合 次(每次离开后可能再次进入,最多离开 次)。每次进入时,会将其所有邻居加入候选队列。
- 每个顶点度数之和 =
- 总候选数
- 坏时刻总数
因此总时间复杂度 ,空间复杂度 。
示例解释
示例1
,敌人从 到 :
- 敌人路径:
- 时刻 :敌人在 ,Marat 在
- 时刻 :敌人在 ,Marat 可到
- 时刻 :敌人在 ,Marat 可到
- 时刻 :敌人在 ,Marat 可到 (到达)
输出 。
示例2
,敌人从 到 :
- 如果 Marat 直接走,时刻 在 相遇
- 在 等待 1 时刻:时刻 在 ,时刻 在 ,时刻 在 ,时刻 在 ,时刻 在
- 敌人时刻 在 , 在 , 在 , 在 ,无冲突
输出 。
总结
本题的核心技巧:
- 利用答案上界 限制搜索范围
- 维护可达顶点集合而非完整 DP 表
- 利用坏时刻总数 的性质
- 每个顶点最多进入集合 次,保证总复杂度
对于 的数据范围, 是可以接受的。
- 1
信息
- ID
- 6560
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 9
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者