2 条题解

  • 0
    @ 2025-10-22 23:41:07

    题目分析

    本题是一个结合图论与时间调度的最短路径问题。核心挑战在于:

    1. 小偷需要从节点1到达节点N,每条边的通行时间为1分钟
    2. 存在K个守卫,每个守卫在固定路径上循环移动
    3. 小偷的路径必须时刻避开守卫所在的节点和正在经过的边
    4. 需找到最短时间或判断无解

    关键约束:

    • 守卫路径不相交
    • 起点(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),需检查:

    1. t时刻u是否有守卫
    2. t+1时刻v是否有守卫
    3. [t, t+1]期间边(u,v)是否被守卫经过

    算法步骤

    1. 预处理守卫信息

      • 对每个守卫i,记录其路径节点序列和边序列
      • 对每个节点/边,记录所有守卫占据它的时间模式(周期+偏移)
    2. 构建时间依赖图

      • 原始图的边是否可用取决于时间
      • 同一节点可在不同时间状态下存在差异
    3. 改进的Dijkstra算法

      • 优先队列存储(到达时间, 当前节点)
      • 对每个节点u,若新到达时间t' < 已知最早时间,则处理:
        • 尝试等待(在u停留1分钟,t'+1时刻仍在u)
        • 尝试移动到邻接节点v(检查t'到t'+1期间的冲突)
    4. 终止条件

      • 到达节点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),用于存储图结构和守卫信息。

    关键优化

    1. 周期冲突检测:利用模运算判断时间冲突,避免存储所有时间点
    2. 优先队列:确保总是处理最早到达的状态
    3. 剪枝:对每个节点只保留最早到达时间,减少冗余计算

    该算法能够有效处理守卫的周期性移动,并找到小偷的最短路径,或判断无解情况。

    • 0
      @ 2025-10-18 23:48:08

      解题步骤

      1. 问题分析 • 小偷需要从节点 1 到节点 N,移动过程中不能与守卫在同一节点或同一条边上 • 守卫按固定路径周期性移动,周期等于其路径长度 • 需计算最短移动时间,或判断无解
      2. 核心思路 • 状态表示:使用(当前节点, 当前时间)表示状态 • BFS 搜索:通过广度优先搜索寻找最短时间,保证首次到达节点 N 的时间为最小值 • 冲突检测:对每个潜在移动,检查是否与守卫的位置或路径冲突
      3. 关键实现 • 预处理守卫的节点占用和边占用信息(周期 + 偏移量) • 对每个状态考虑两种选择:等待(停留 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

      「BalticOI 2021 Day1」From Hacks to Snitches

      信息

      ID
      3170
      时间
      3000ms
      内存
      1024MiB
      难度
      10
      标签
      递交数
      10
      已通过
      1
      上传者