解析:完善程序-1

代码分析:

  1. 问题背景:这段代码实现的是一个在有向无环图(DAG)中,找到按字典序排序的第k条最短路径的算法。输入是n个节点和m条边的DAG,以及一个整数k。目标是找到第k条最短路径的起点,并按照字典序输出后续路径上的节点。
  2. 数据结构
    • E[u] 是一个邻接表,存储从节点u到其他节点的边。
    • deg[i] 记录每个节点的入度,方便进行拓扑排序。
    • f[u] 记录从节点u开始的所有可能路径的数量,用于判断第k条路径起点的选择。
  3. 核心算法
    • 拓扑排序:
      • 将入度为0的节点入队,进行拓扑排序。
      • 拓扑排序的目的是为了按照节点的依赖关系进行遍历,确保在计算路径数量时,所有前驱节点的路径数量都已经计算完毕。
    • 动态规划计算路径数量:
      • 从拓扑排序的结果逆序遍历节点。
      • 对于每个节点u,计算从u出发到所有后继节点的路径数量之和,并将其存储在f[u]中。
      • 为了防止路径数量过大,引入了一个上限LTM。
    • 查找第k小的路径起点:
      • 使用next函数,在候选节点中找到第k小的路径的起点。
      • next函数通过对候选节点进行排序,并逐步减去每个节点对应的路径数量,来定位第k小的路径。
      • 找到起点后,输出,并继续查找第k-1小的路径,直到k=1。

代码实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**************************************************************** 
 * Description: 2023_CSP_S_F01
 * Author: Alex Li
 * Date: 2024-06-18 13:56:33
 * LastEditTime: 2024-06-18 19:16:24
****************************************************************/
#include <iostream> 
#include <algorithm>
#include <vector>

const int MAXN = 100000;               // 定义最大节点数
const long long LTM = 1000000000000000000; // 定义一个很大的数,用于限制路径数量的上限
int n, m, deg[MAXN];                   // n: 节点数, m: 边数, deg: 每个节点的入度数组
std::vector<int> E[MAXN];              // 邻接表存储图的边关系
long long k, f[MAXN];                  // k: 要查找的第k小路径,f[i]: 从节点i开始的路径数量

// 在后选点中,找第k小的路径的起点
int next(std::vector<int> cand, long long &k) {
    std::sort(cand.begin(), cand.end()); // 对候选节点进行排序
    for(int u : cand) {                  // 遍历排序后的候选节点
        if(k <= f[u]) return u;          // 如果k在当前节点u对应的路径数量范围内,返回u作为起点
        k -= f[u];                       // 否则减去该节点对应的路径数量,继续下一个候选节点
    }
    return -1;                           // 如果找不到合适的节点,返回-1
}

int main() {
    std::cin >> n >> m >> k;             // 读入节点数n,边数m,和需要查询的第k小路径
    for(int i = 0; i < m; ++i) {
        int u, v;
        std::cin >> u >> v;              // 读入每条边的起点u和终点v
        E[u].push_back(v);               // 将边添加到邻接表E中,表示从u到v有一条边
        ++deg[v];                        // 增加节点v的入度
    }

    // 拓扑排序
    std::vector<int> Q;                  // 存储拓扑排序的结果
    for(int i = 0; i < n; ++i)
        if(!deg[i]) Q.push_back(i);      // 将所有入度为0的节点加入队列Q

    for(int i = 0; i < n; ++i) {
        int u = Q[i];                    // 依次处理拓扑序中的每个节点
        for(int v : E[u]) {              // 遍历该节点u的所有邻接节点v
            if(deg[v] == 1)              // 如果邻接节点v的入度为1,说明v的前驱都已处理
                Q.push_back(v);          // 将v加入拓扑序队列
            --deg[v];                    // 减少节点v的入度
        }
    }
    std::reverse(Q.begin(), Q.end());    // 将拓扑序逆序,方便后续动态规划计算路径数量

    // 动态规划计算f(i): 从i节点开始的路径数量
    for(int u : Q) {
        f[u] = 1;                        // 每个节点的路径数量初始为1(即单个节点本身)
        for(int v : E[u])                // 遍历该节点u的所有邻接节点v
            f[u] = std::min(f[u] + f[v], LTM); // 累加从v开始的路径数量,避免超过上限LTM
    }

    // 查找第k小的路径的起点
    int u = next(Q, k);
    std::cout << u << std::endl;         // 输出起点u

    // 如果需要依次找到第k-1, k-2的路径
    while(k > 1) {
        --k;                             // 递减k
        u = next(E[u], k);               // 查找当前节点u的邻接节点中第k小的路径
        std::cout << u << std::endl;     // 输出当前的节点
    }

    return 0;
}
Scroll to Top