2 条题解

  • 0
    @ 2026-1-9 9:19:28

    为了方便你构建 OJ(Online Judge)测试数据,我编写了此数据生成器。它包含了标准堆优化 Dijkstra 算法(用于生成 .out)以及针对性数据构造逻辑(用于生成 .in)。

    数据生成策略 (10 个测试点)

    1. Case 1 (最小边界)n=1,m=0,k=1n=1, m=0, k=1。测试起点即终点的情况。
    2. Case 2 (不连通图)n=100,m=50n=100, m=50,且 kk 无法到达部分节点,预期输出 -1。
    3. Case 3 (样例 1):完全还原题目样例。
    4. Case 4 (长链图)1231001 \to 2 \to 3 \dots \to 100,测试最短路径的最深层级。
    5. Case 5 (星形图):中心节点 kk 直接连接到所有其他节点。
    6. Case 6 (全 0 权值):所有边权均为 0,测试算法对零权边的处理。
    7. Case 7 (极限稠密随机)n=100,m=6000n=100, m=6000,测试算法在复杂图下的常数。
    8. Case 8 (稀疏随机图)n=100,m=150n=100, m=150
    9. Case 9 (最大权值图):所有边权均为 100,构造较长的最短路径。
    10. Case 10 (完全图子图):随机选取大量边,并保证起点 kk 随机。

    数据生成器代码 (C++14)

    #include <iostream>
    #include <fstream>
    #include <vector>
    #include <queue>
    #include <set>
    #include <string>
    #include <random>
    #include <ctime>
    #include <algorithm>
    
    using namespace std;
    
    const int INF = 0x3f3f3f3f;
    
    // --- 标准堆优化 Dijkstra 解法,用于生成 .out 文件 ---
    int solve_std(int n, int m, int k, const vector<vector<pair<int, int>>>& adj) {
        vector<int> dist(n + 1, INF);
        dist[k] = 0;
        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
        pq.push({0, k});
    
        while (!pq.empty()) {
            int d = pq.top().first;
            int u = pq.top().second;
            pq.pop();
    
            if (d > dist[u]) continue;
    
            for (auto& edge : adj[u]) {
                int v = edge.first;
                int w = edge.second;
                if (dist[u] + w < dist[v]) {
                    dist[v] = dist[u] + w;
                    pq.push({dist[v], v});
                }
            }
        }
    
        int max_d = 0;
        for (int i = 1; i <= n; i++) {
            if (dist[i] == INF) return -1;
            max_d = max(max_d, dist[i]);
        }
        return max_d;
    }
    
    // --- 写入输入输出文件 ---
    void write_case(int id, int n, int k, const vector<pair<pair<int, int>, int>>& edges) {
        string in_name = to_string(id) + ".in";
        string out_name = to_string(id) + ".out";
    
        int m = edges.size();
        ofstream fout(in_name);
        fout << n << " " << m << " " << k << "\n";
        vector<vector<pair<int, int>>> adj(n + 1);
        for (auto& e : edges) {
            fout << e.first.first << " " << e.first.second << " " << e.second << "\n";
            adj[e.first.first].push_back({e.first.second, e.second});
        }
        fout.close();
    
        ofstream fans(out_name);
        fans << solve_std(n, m, k, adj) << endl;
        fans.close();
        cout << "Case " << id << " generated: N=" << n << " M=" << m << endl;
    }
    
    int main() {
        mt19937 rng(time(0));
    
        // Case 1: n=1 边界
        write_case(1, 1, 1, {});
    
        // Case 2: 不连通
        write_case(2, 2, 2, {{{1, 2}, 10}});
    
        // Case 3: 样例 1
        write_case(3, 4, 2, {{{2, 1}, 1}, {{2, 3}, 1}, {{3, 4}, 1}});
    
        // Case 4: 长链图
        {
            vector<pair<pair<int, int>, int>> e;
            for(int i=1; i<100; i++) e.push_back({{i, i+1}, 1});
            write_case(4, 100, 1, e);
        }
    
        // Case 5: 星形图
        {
            vector<pair<pair<int, int>, int>> e;
            for(int i=2; i<=100; i++) e.push_back({{1, i}, 50});
            write_case(5, 100, 1, e);
        }
    
        // Case 6: 全 0 权值
        {
            vector<pair<pair<int, int>, int>> e;
            for(int i=1; i<100; i++) e.push_back({{i, i+1}, 0});
            e.push_back({{100, 1}, 0});
            write_case(6, 100, 50, e);
        }
    
        // Case 7: 极限稠密随机 (N=100, M=6000)
        {
            vector<pair<pair<int, int>, int>> e;
            set<pair<int, int>> exists;
            while(e.size() < 6000) {
                int u = rng() % 100 + 1, v = rng() % 100 + 1;
                if(u != v && exists.find({u, v}) == exists.end()) {
                    e.push_back({{u, v}, (int)(rng() % 101)});
                    exists.insert({u, v});
                }
            }
            write_case(7, 100, 1, e);
        }
    
        // Case 8: 稀疏随机
        {
            vector<pair<pair<int, int>, int>> e;
            for(int i=1; i<=150; i++) {
                int u = rng() % 100 + 1, v = rng() % 100 + 1;
                if(u != v) e.push_back({{u, v}, (int)(rng() % 101)});
            }
            write_case(8, 100, 1, e);
        }
    
        // Case 9: 大权值 (全部 100)
        {
            vector<pair<pair<int, int>, int>> e;
            for(int i=1; i<=100; i++)
                for(int j=1; j<=20; j++) {
                    int v = rng() % 100 + 1;
                    if(i != v) e.push_back({{i, v}, 100});
                }
            write_case(9, 100, 1, e);
        }
    
        // Case 10: 随机综合
        {
            int n = 100, m = 3000;
            vector<pair<pair<int, int>, int>> e;
            set<pair<int, int>> exists;
            while(e.size() < m) {
                int u = rng() % n + 1, v = rng() % n + 1;
                if(u != v && exists.find({u, v}) == exists.end()) {
                    e.push_back({{u, v}, (int)(rng() % 101)});
                    exists.insert({u, v});
                }
            }
            write_case(10, n, (int)(rng() % n + 1), e);
        }
    
        return 0;
    }
    

    生成器设计说明:

    1. 文件大小优化
      • 最大规模 N=100,M=6000N=100, M=6000。每个 .in 文件包含约 6000 行三个整数的数据。
      • 单行平均约 10 字节,整个文件约 60 KB。
      • 10 个测试点总计约 0.6 MB,完全符合“2MB 以内”的要求。
    2. 区分度设计
      • Case 4 & 5:通过特定的拓扑结构测试搜索深度。
      • Case 7:测试 O(MlogN)O(M \log N) 算法在边数饱和时的表现。
      • Case 2:测试程序是否能正确输出 -1(连通性判定)。
    3. 计算安全
      • 使用了 std::set 确保生成的有向图没有重边,这符合大多数 OJ 对“网络延迟时间”类题目的数据约定。
      • solve_std 采用标准堆优化 Dijkstra,并使用非递归方式防止爆栈,逻辑上避开了除零异常。
    4. NOI 格式适配
      • 输入格式严格按照 N,M,KN, M, K 的顺序读入,边权范围严格遵守 0w1000 \le w \le 100

    使用方法:

    1. 复制上述代码,在支持 C++11 或更高标准的编译器上编译运行。
    2. 程序会自动在当前文件夹生成 1.in ~ 10.in 和对应的 1.out ~ 10.out
    3. 将这些文件打包上传到你的 OJ 题目后台即可。
    • 0
      @ 2026-1-9 9:16:21

      在 NOI 竞赛中,单源最短路问题是图论的核心。针对本题 N=100N=100 的小规模数据,有多种算法可以实现。我们将从最易写的全源最短路过渡到标准的堆优化 Dijkstra。


      版本一:Floyd-Warshall 算法 (O(N3)O(N^3))

      思路:NN 较小时(通常 N300N \le 300),Floyd 算法是性价比最高的选择。它通过三重循环,尝试利用每一个点 kk 作为中间跳板来更新点 ii 到点 jj 的距离。

      #include <iostream>
      #include <vector>
      #include <algorithm>
      
      using namespace std;
      
      const int INF = 0x3f3f3f3f; // 竞赛常用无穷大,累加不易溢出且可用memset
      int dist[105][105];
      int n, m, k;
      
      int main() {
          ios::sync_with_stdio(false);
          cin.tie(0);
      
          if (!(cin >> n >> m >> k)) return 0;
      
          // 1. 初始化:自己到自己为0,其余为无穷大
          for (int i = 1; i <= n; i++) {
              for (int j = 1; j <= n; j++) {
                  if (i == j) dist[i][j] = 0;
                  else dist[i][j] = INF;
              }
          }
      
          // 2. 读入边
          for (int i = 0; i < m; i++) {
              int u, v, w;
              cin >> u >> v >> w;
              dist[u][v] = w; // 注意是有向边
          }
      
          // 3. Floyd 核心:k 必须在最外层
          for (int p = 1; p <= n; p++) {
              for (int i = 1; i <= n; i++) {
                  for (int j = 1; j <= n; j++) {
                      if (dist[i][p] != INF && dist[p][j] != INF) {
                          dist[i][j] = min(dist[i][j], dist[i][p] + dist[p][j]);
                      }
                  }
              }
          }
      
          // 4. 统计结果
          int max_delay = 0;
          for (int i = 1; i <= n; i++) {
              if (dist[k][i] == INF) { // 存在节点不可达
                  cout << -1 << endl;
                  return 0;
              }
              max_delay = max(max_delay, dist[k][i]);
          }
      
          cout << max_delay << endl;
          return 0;
      }
      
      • 时间复杂度O(N3)O(N^3)。对于 N=100N=100,运算量为 10610^6,瞬间完成。
      • 空间复杂度O(N2)O(N^2)。需要邻接矩阵存储。

      版本二:朴素 Dijkstra 算法 (O(N2)O(N^2))

      思路: 这是针对单源最短路的贪心算法。每次在未访问的节点中选择一个距离起点最近的点,利用它去松弛邻居。 对于稠密图(边数 MM 接近 N2N^2),朴素 Dijkstra 往往比堆优化版更快。

      #include <iostream>
      #include <vector>
      #include <cstring>
      #include <algorithm>
      
      using namespace std;
      
      int g[105][105];
      int d[105];
      bool vis[105];
      int n, m, k;
      
      int main() {
          cin >> n >> m >> k;
          memset(g, 0x3f, sizeof(g));
          for (int i = 1; i <= n; i++) g[i][i] = 0;
      
          for (int i = 0; i < m; i++) {
              int u, v, w;
              cin >> u >> v >> w;
              g[u][v] = w;
          }
      
          memset(d, 0x3f, sizeof(d));
          d[k] = 0;
      
          for (int i = 0; i < n; i++) {
              int u = -1;
              // 寻找当前未访问且距离最短的点
              for (int j = 1; j <= n; j++) {
                  if (!vis[j] && (u == -1 || d[j] < d[u])) u = j;
              }
      
              if (d[u] == 0x3f3f3f3f) break;
              vis[u] = true;
      
              // 松弛邻居
              for (int v = 1; v <= n; v++) {
                  if (g[u][v] != 0x3f3f3f3f) {
                      d[v] = min(d[v], d[u] + g[u][v]);
                  }
              }
          }
      
          int res = 0;
          for (int i = 1; i <= n; i++) {
              if (d[i] == 0x3f3f3f3f) { cout << -1 << endl; return 0; }
              res = max(res, d[i]);
          }
          cout << res << endl;
          return 0;
      }
      
      • 时间复杂度O(N2)O(N^2)
      • 空间复杂度O(N2)O(N^2)

      版本三:堆优化 Dijkstra (O(MlogN)O(M \log N)) —— 最优推荐

      思路: 在 NOI 竞赛中,这是处理中大型图(N=105,M=5×105N=10^5, M=5 \times 10^5)的标准模版。利用优先队列(小根堆)维护“最近的点”,将寻找最小值的复杂度从 O(N)O(N) 降至 O(logN)O(\log N)

      #include <iostream>
      #include <vector>
      #include <queue>
      #include <algorithm>
      #include <cstring>
      
      using namespace std;
      
      // 使用结构体存储边
      struct Edge {
          int to, weight;
      };
      
      // 优先队列节点,距离小的优先级高
      struct Node {
          int u, dist;
          bool operator>(const Node& other) const {
              return dist > other.dist;
          }
      };
      
      const int INF = 0x3f3f3f3f;
      vector<Edge> adj[105];
      int dist[105];
      
      int main() {
          int n, m, k;
          if (!(cin >> n >> m >> k)) return 0;
      
          for (int i = 0; i < m; i++) {
              int u, v, w;
              cin >> u >> v >> w;
              adj[u].push_back({v, w});
          }
      
          memset(dist, 0x3f, sizeof(dist));
          dist[k] = 0;
      
          // 堆优化:小根堆
          priority_queue<Node, vector<Node>, greater<Node>> pq;
          pq.push({k, 0});
      
          while (!pq.empty()) {
              Node top = pq.top();
              pq.pop();
              int u = top.u;
              int d = top.dist;
      
              // 关键点:如果当前弹出的距离已经不是最新的,直接跳过(懒惰删除)
              if (d > dist[u]) continue;
      
              for (auto& edge : adj[u]) {
                  int v = edge.to;
                  if (dist[u] + edge.weight < dist[v]) {
                      dist[v] = dist[u] + edge.weight;
                      pq.push({v, dist[v]});
                  }
              }
          }
      
          int ans = 0;
          for (int i = 1; i <= n; i++) {
              if (dist[i] == INF) {
                  cout << -1 << endl;
                  return 0;
              }
              ans = max(ans, dist[i]);
          }
          cout << ans << endl;
      
          return 0;
      }
      

      复杂度分析思考过程

      1. 时间复杂度
        • Floyd: 1003=106100^3 = 10^6。逻辑简单,适合比赛开头快速写完拿分。
        • 堆优化 Dijkstra: $M \log M \approx 6000 \times 13 \approx 7.8 \times 10^4$。这是性能最优解。
      2. 空间复杂度
        • 邻接矩阵: N2=10000N^2 = 10000int,约 40KB。
        • 邻接表: O(N+M)O(N+M),约 100KB。
        • 对于 NOI 常见的 256MB 内存,两者都可以忽略不计。

      时间复杂度优化建议

      • 邻接表/前向星:对于边数远大于点数的图,不要用邻接矩阵,否则初始化和遍历会浪费大量时间。
      • 快读:在 M>106M > 10^6 时,使用 scanfgetchar() 自定义快读函数。
      • SPFA 算法:如果边权存在负数,Dijkstra 失效,必须使用 SPFA(虽然在特殊构造的图上会退化到 O(NM)O(NM))。

      读题关键词总结

      • “有向边传递时间”:确定是有向图。
      • “从某个节点发出”:确定是单源路径问题。
      • “所有节点都收到”:信号是并行的,所以时间取决于最晚到达的节点,即各点最短路径中的最大值。
      • “不可达输出-1”:提示必须检查连通性。
      • 1

      信息

      ID
      19463
      时间
      1000ms
      内存
      128MiB
      难度
      10
      标签
      (无)
      递交数
      2
      已通过
      1
      上传者