2 条题解
-
0
题目分析
本题是一个结合图论与时间调度的最短路径问题。核心挑战在于:
- 小偷需要从节点1到达节点N,每条边的通行时间为1分钟
- 存在K个守卫,每个守卫在固定路径上循环移动
- 小偷的路径必须时刻避开守卫所在的节点和正在经过的边
- 需找到最短时间或判断无解
关键约束:
- 守卫路径不相交
- 起点(1)和终点(N)不在任何守卫路径上
- 守卫移动是周期性的(循环其路径)
核心思路
1. 建模守卫的时空占据情况
每个守卫i的周期为T_i = ℓ_i(路径长度),在t分钟时:
- 位置在节点v_{(t mod T_i)+1}
- 正在经过边(v_{(t mod T_i)+1}, v_{(t+1 mod T_i)+1})
需要预先计算:
- 每个节点被守卫占据的时间集合
- 每条边被守卫经过的时间集合
2. 时间依赖的最短路径
采用BFS变种求解,状态为(当前节点, 当前时间),记录到达每个节点的最早时间。
优化点:
- 由于守卫周期特性,可利用取模运算判断时间冲突
- 对每个节点维护最早到达时间,避免重复访问
3. 冲突检测
对于移动方案(t时刻从u到v),需检查:
- t时刻u是否有守卫
- t+1时刻v是否有守卫
- [t, t+1]期间边(u,v)是否被守卫经过
算法步骤
-
预处理守卫信息:
- 对每个守卫i,记录其路径节点序列和边序列
- 对每个节点/边,记录所有守卫占据它的时间模式(周期+偏移)
-
构建时间依赖图:
- 原始图的边是否可用取决于时间
- 同一节点可在不同时间状态下存在差异
-
改进的Dijkstra算法:
- 优先队列存储(到达时间, 当前节点)
- 对每个节点u,若新到达时间t' < 已知最早时间,则处理:
- 尝试等待(在u停留1分钟,t'+1时刻仍在u)
- 尝试移动到邻接节点v(检查t'到t'+1期间的冲突)
-
终止条件:
- 到达节点N时返回当前时间
- 队列空仍未到达N则返回impossible
代码实现
import sys import heapq from collections import defaultdict def main(): input = sys.stdin.read().split() ptr = 0 N, M = int(input[ptr]), int(input[ptr+1]) ptr += 2 # 建图 adj = [[] for _ in range(N+1)] # 1-based edges = set() for _ in range(M): u, v = int(input[ptr]), int(input[ptr+1]) ptr += 2 adj[u].append(v) adj[v].append(u) if u > v: u, v = v, u edges.add((u, v)) K = int(input[ptr]) ptr += 1 # 守卫信息: 节点占据和边占据 node_guards = defaultdict(list) # node -> list of (period, offset) edge_guards = defaultdict(list) # (u,v) -> list of (period, offset), u < v for _ in range(K): ℓ_i = int(input[ptr]) ptr += 1 path = list(map(int, input[ptr:ptr+ℓ_i])) ptr += ℓ_i T = ℓ_i # 周期 # 记录节点占据: t时刻在path[(t mod T)] for t_offset in range(T): node = path[t_offset] node_guards[node].append((T, t_offset)) # 记录边占据: t时刻经过path[t mod T] -> path[(t+1) mod T] for t_offset in range(T): u = path[t_offset] v = path[(t_offset + 1) % T] if u > v: u, v = v, u edge_guards[(u, v)].append((T, t_offset)) # Dijkstra: (到达时间, 节点) INF = float('inf') dist = [INF] * (N + 1) dist[1] = 0 heap = [] heapq.heappush(heap, (0, 1)) while heap: t, u = heapq.heappop(heap) if u == N: print(t) return if t > dist[u]: continue # 选项1: 等待1分钟 new_t = t + 1 # 检查new_t时刻u是否有守卫 conflict = False for (period, offset) in node_guards.get(u, []): if new_t % period == offset: conflict = True break if not conflict and new_t < dist[u]: dist[u] = new_t heapq.heappush(heap, (new_t, u)) # 选项2: 移动到邻接节点 for v in adj[u]: a, b = u, v if a > b: a, b = b, a # 检查: # 1. t时刻u是否有守卫 ok = True for (period, offset) in node_guards.get(u, []): if t % period == offset: ok = False break if not ok: continue # 2. t+1时刻v是否有守卫 for (period, offset) in node_guards.get(v, []): if (t + 1) % period == offset: ok = False break if not ok: continue # 3. [t, t+1]期间边(a,b)是否被守卫经过 for (period, offset) in edge_guards.get((a, b), []): if t % period == offset: ok = False break if not ok: continue # 移动可行 new_t = t + 1 if new_t < dist[v]: dist[v] = new_t heapq.heappush(heap, (new_t, v)) # 无法到达 print("impossible") if __name__ == "__main__": main()复杂度分析
- 时间复杂度:O((N + M) × L × log N),其中L是守卫周期的最大公倍数。实际中由于使用了优先队列和最早时间剪枝,效率会更高。
- 空间复杂度:O(N + M + ∑ℓ_i),用于存储图结构和守卫信息。
关键优化
- 周期冲突检测:利用模运算判断时间冲突,避免存储所有时间点
- 优先队列:确保总是处理最早到达的状态
- 剪枝:对每个节点只保留最早到达时间,减少冗余计算
该算法能够有效处理守卫的周期性移动,并找到小偷的最短路径,或判断无解情况。
-
0
解题步骤
- 问题分析 • 小偷需要从节点 1 到节点 N,移动过程中不能与守卫在同一节点或同一条边上 • 守卫按固定路径周期性移动,周期等于其路径长度 • 需计算最短移动时间,或判断无解
- 核心思路 • 状态表示:使用(当前节点, 当前时间)表示状态 • BFS 搜索:通过广度优先搜索寻找最短时间,保证首次到达节点 N 的时间为最小值 • 冲突检测:对每个潜在移动,检查是否与守卫的位置或路径冲突
- 关键实现 • 预处理守卫的节点占用和边占用信息(周期 + 偏移量) • 对每个状态考虑两种选择:等待(停留 1 分钟)或移动到相邻节点(耗时 1 分钟) • 冲突检测通过模运算判断时间是否落在守卫的周期内 代码实现 #include <bits/stdc++.h> using namespace std;
typedef long long ll; const ll INF = 1e18;
// 用于存储守卫的周期和禁止时间 struct GuardInfo { int period; int forbidden; };
// 边的表示(用排序的pair确保无向性) using Edge = pair<int, int>; Edge make_edge(int u, int v) { if (u > v) swap(u, v); return {u, v}; }
int main() { ios::sync_with_stdio(false); cin.tie(0);
int N, M; cin >> N >> M; // 邻接表 vector<vector<int>> adj(N + 1); // 边的守卫信息 map<Edge, vector<GuardInfo>> edge_guards; for (int i = 0; i < M; ++i) { int u, v; cin >> u >> v; adj[u].push_back(v); adj[v].push_back(u); Edge e = make_edge(u, v); edge_guards[e]; // 初始化空向量 } // 节点的守卫信息 vector<vector<GuardInfo>> node_guards(N + 1); int K; cin >> K; for (int i = 0; i < K; ++i) { int l; cin >> l; vector<int> path(l); for (int j = 0; j < l; ++j) { cin >> path[j]; } // 记录节点被守卫占据的时间 for (int t = 0; t < l; ++t) { int u = path[t]; node_guards[u].push_back({l, t}); } // 记录边被守卫经过的时间(t时刻开始经过边) for (int t = 0; t < l; ++t) { int u = path[t]; int v = path[(t + 1) % l]; Edge e = make_edge(u, v); if (edge_guards.count(e)) { edge_guards[e].push_back({l, t}); } } } // BFS:dist[u]表示到达u的最早时间 vector<ll> dist(N + 1, INF); dist[1] = 0; queue<pair<int, ll>> q; q.push({1, 0}); bool found = false; while (!q.empty()) { auto [u, t] = q.front(); q.pop(); // 到达终点 if (u == N) { cout << t << endl; found = true; break; } // 已找到更优路径,跳过 if (t > dist[u]) continue; // 选项1:等待1分钟 ll new_t = t + 1; bool conflict = false; for (auto [p, f] : node_guards[u]) { if ((new_t - f) % p == 0) { conflict = true; break; } } if (!conflict && new_t < dist[u]) { dist[u] = new_t; q.push({u, new_t}); } // 选项2:移动到相邻节点 for (int v : adj[u]) { Edge e = make_edge(u, v); // 检查边在t时刻是否被占用 bool edge_conflict = false; if (edge_guards.count(e)) { for (auto [p, f] : edge_guards[e]) { if ((t - f) % p == 0) { edge_conflict = true; break; } } } if (edge_conflict) continue; // 检查目标节点v在new_t时刻是否被占用 ll new_t_v = t + 1; bool node_conflict = false; for (auto [p, f] : node_guards[v]) { if ((new_t_v - f) % p == 0) { node_conflict = true; break; } } if (node_conflict) continue; // 可以移动 if (new_t_v < dist[v]) { dist[v] = new_t_v; q.push({v, new_t_v}); } } } if (!found) { cout << "impossible" << endl; } return 0;}
- 1
信息
- ID
- 3170
- 时间
- 3000ms
- 内存
- 1024MiB
- 难度
- 10
- 标签
- 递交数
- 10
- 已通过
- 1
- 上传者