图论

1 图论

1.1 图的建立

1.1.1 领接表边权建图

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {
    // 定义图的邻接表表示
    static List<int[]>[] g;
    // 节点数
    static int n;
    // 保存某种状态或结果的数组
    static int[] f;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // 读取节点数
        n = scanner.nextInt();
        
        // 初始化图的邻接表
        g = new ArrayList[n + 1];
        for (int i = 1; i <= n; i++) {
            g[i] = new ArrayList<>();
        }
        
        // 初始化状态数组
        f = new int[n + 1];

        // 读取边的信息并构建图
        for (int i = 1; i < n; i++) {
            int a = scanner.nextInt(); // 边的一个端点
            int b = scanner.nextInt(); // 边的另一个端点
            int c = scanner.nextInt(); // 边的权重
            // 添加无向边到邻接表
            g[a].add(new int[]{b, c});
            g[b].add(new int[]{a, c});
        }
    }

    // 深度优先搜索(DFS)遍历图
    static void dfs(int u, int fa) {
        // 遍历节点u的所有邻接节点
        for (int[] pair : g[u]) {
            int x = pair[0]; // 邻接节点
            int w = pair[1]; // 边的权重
            // 如果邻接节点是父节点,则跳过(避免走回头路)
            if (x == fa) continue;
            // 递归遍历邻接节点
            dfs(x, u);
            // 这里可以添加其他的逻辑处理代码
        }
    }
}

  

1.1.2 领接表点权建图

import java.util.Scanner;
import java.util.ArrayList;

public class Main {
    // 定义常量N为节点数的上限
    static final int N = 100010;
    // 用于存储图的邻接表表示
    static ArrayList<Integer>[] g = new ArrayList[N];
    // 用于存储节点的权值
    static int[] w = new int[N];

    // 深度优先搜索(DFS)遍历图
    static void dfs(int u, int fa) {
        // 处理节点u的操作
        // 遍历节点u的所有邻接节点
        for (int x : g[u]) {
            // 如果邻接节点是父节点,则跳过(避免走回头路)
            if (x == fa)
                continue;
            // 递归遍历邻接节点
            dfs(x, u);
            // 处理子节点返回后的操作
        }
    }

    public static void main(String[] args) {
        int n, m;
        Scanner scanner = new Scanner(System.in);

        // 读取节点数n和边数m
        n = scanner.nextInt();
        m = scanner.nextInt();

        // 初始化邻接表
        for (int i = 1; i <= n; i++) {
            g[i] = new ArrayList<>();
        }

        // 读取边的信息并构建图
        for (int i = 0; i < m; i++) {
            int a, b;
            a = scanner.nextInt();
            b = scanner.nextInt();
            g[a].add(b);  // 添加一条从a到b的边
        }

        // 读取每个节点的权值
        for (int i = 1; i <= n; i++) {
            w[i] = scanner.nextInt();
        }

        // 可以在这里调用dfs进行深度优先搜索,例如从节点1开始
        // dfs(1, -1);
    }
}

  

1.1.3 离散化建图

  1. 点的范围很大(例如 [−109,109]),或者含有负数的点。
  2. 点不是数值,而是字符串,字符串与字符串之间存在相互转换的关系。
import java.util.*;

public class Main {
    public static void main(String[] args) {
        // 定义一个常量N,表示可能的最大节点数
        int N = 100010;
        
        // 定义一个哈希表,用于存储节点之间的边及其权重
        // 键是节点,值是一个包含目标节点及边权的列表
        Map<Integer, List<Map.Entry<Integer, Integer>>> path = new HashMap<>();
        
        // 示例:添加从节点u到节点v,边的权重为w
        int u = 1, v = 2, w = 3;
        
        // 如果哈希表中不包含键u,初始化其值为一个新的ArrayList
        if (!path.containsKey(u)) {
            path.put(u, new ArrayList<>());
        }
        
        // 将边(v, w)添加到节点u的列表中
        path.get(u).add(new AbstractMap.SimpleEntry<>(v, w));
        
        // 示例:遍历节点node的所有相邻节点和边权
        int node = 1;
        
        // 如果哈希表中包含键node,进行遍历
        if (path.containsKey(node)) {
            for (Map.Entry<Integer, Integer> entry : path.get(node)) {
                int target = entry.getKey();  // 相邻节点
                int weight = entry.getValue(); // 边的权重
                // 进行处理,例如打印或其他逻辑
                System.out.println("从节点 " + node + " 到节点 " + target + " 的边权是 " + weight);
            }
        }
    }
}

  

1.2 拓扑排序

 用于判断有向图中是否存在环

拓扑排序模板题
根据依赖关系建图:先学习课程a必须要先学习课程b,则有b->a这一条有向边。如果可以完成所有课程,则说明存在拓扑排序。
 
import java.util.*;

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        // 创建图的邻接表表示
        List<List<Integer>> g = new ArrayList<>();
        for (int i = 0; i < numCourses; i++) {
            g.add(new ArrayList<>()); // 为每个节点初始化邻接表
        }
        
        // 创建数组存储每个节点的入度
        int[] d = new int[numCourses];
        
        // 构建图并计算每个节点的入度
        for (int[] p : prerequisites) {
            g.get(p[1]).add(p[0]); // 添加从节点p[1]到节点p[0]的边
            d[p[0]]++; // 增加节点p[0]的入度
        }
        
        // 初始化队列用于BFS
        Queue<Integer> q = new LinkedList<>();
        int cnt = 0; // 计数器,记录可以遍历的节点数量
        
        // 将所有入度为0的节点添加到队列中
        for (int i = 0; i < numCourses; i++) {
            if (d[i] == 0) {
                q.offer(i);
                cnt++;
            }
        }
        
        // 执行BFS处理节点
        while (!q.isEmpty()) {
            int t = q.poll(); // 从队列中取出一个节点
            for (int v : g.get(t)) { // 遍历所有依赖于t的节点
                d[v]--; // 减少依赖节点的入度
                if (d[v] == 0) { // 如果依赖节点的入度变为0,将其加入队列
                    q.offer(v);
                    cnt++;
                }
            }
        }
        
        // 如果处理的节点数量等于总节点数,则表示可以遍历所有节点,否则不能
        return cnt == numCourses;
    }
}

 

华为实习2023042601

开发一个新的3D引擎,包含多个模块,每个模块负责不同的功能,比如渲染、物理、音效、网络等。 为了提高性能和稳定性,需要在游戏启动时初始化这些模块。
但是不同的模块之间存在依赖关系,比如渲染模块依赖于物理模块,音效模块依赖于网络模块等。如果不按照正确的顺序初始化这些模块,就会导致错误或崩溃。
需要开发一个代码分析工具,分析代码模块之间的依赖关系,确定模块的初始化顺序,判断是否有循环依赖等问题。 工具可以一次初始化一个或多个模块,只要它们之间没有依赖关系。
这个过程称为引擎模块初始化。 已经得到了一组模块间的依赖关系,需要计算引擎模块初始化的次数。

 

输入描述

  • 第一行是一个整数 `n`,表示模块总数。
  • 接下来的 `n` 行表示模块 `1` 到 `n` 的依赖关系。
  • 每行的第一个数是一个整数 `m`,表示依赖的模块数量,之后的数字表示当前模块依赖的模块ID。
  • 每一行的数字按空格分隔。

约束条件:

  • 1 ≤ m ≤ n ≤ 1000

输出描述

  • 输出批量初始化次数。
  • 若有循环依赖无法完成初始化,则输出 `-1`

样例

样例一

输入

5
3 2 3 4
1 5
1 5
1 5
0

  输出

3

解释

共 5 个模块。

模块 `1` 依赖模块 `2`、`3`、`4`;

模块 `2` 依赖模块 `5`;

模块 `3` 依赖模块 `5`;

模块 `4` 依赖模块 `5`;

模块 `5` 没有依赖。

批量初始化顺序为 `{5} -> {2, 3, 4} -> {1}`,共需 3 次批量初始化。

 

代码 

import java.util.*;

public class Main {
    public void solution(){
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt(); // 读取模块总数
        int[] inDegree = new int[n + 1]; // 入度数组,用于记录每个模块的依赖计数
        inDegree[0] = -1; // 虚拟的0号节点,设置为-1
        ArrayList<Integer>[] outDegree = new ArrayList[n + 1]; // 出度表,用于记录每个模块的被依赖关系

        // 读取每个模块的依赖信息
        for(int i = 1; i <= n; ++i){
            int m = scanner.nextInt(); // 读取当前模块的依赖数量
            inDegree[i] += m; // 更新入度
            for(int j = 0; j < m; ++j){
                int from = scanner.nextInt(); // 读取依赖的模块ID
                if(outDegree[from] == null)
                    outDegree[from] = new ArrayList<>();
                outDegree[from].add(i); // 将当前模块添加到依赖列表中
            }
        }
        scanner.close(); 

        int res = 0; // 记录批量初始化次数
        boolean check; // 用于判断当前批次是否有可初始化的模块
        Queue<Integer> queue = new LinkedList<>(); // 队列用于BFS

        // 执行拓扑排序
        do {
            check = false;
            // 找出所有入度为0的模块
            for(int i = 1; i < inDegree.length; ++i){
                if(inDegree[i] == 0){
                    inDegree[i] = -1; // 标记为已处理
                    check = true;
                    queue.offer(i); // 将模块加入队列
                }
            }
            if(check)
                res += 1; // 增加批次计数
            // 处理队列中的模块
            while(!queue.isEmpty()){
                int from = queue.poll(); // 取出队首模块
                if(outDegree[from] == null)
                    continue;
                // 减少依赖模块的入度
                for(int i = 0; i < outDegree[from].size(); ++i)
                    --inDegree[outDegree[from].get(i)];
            }
        }while(check);

        // 检查是否存在未处理的模块
        for(int i = 1; i < inDegree.length; ++i){
            if(inDegree[i] != -1){
                System.out.println(-1); // 存在循环依赖,输出-1
                return;
            }
        }
        System.out.println(res); // 输出批量初始化次数
    }
    public static void main(String[] args) {
        Main m = new Main();
        m.solution(); // 调用解决方案
    }
}

 

华为秋招20230830

系统由n个任务组成,任务运行有依赖关系,前序任务执行完毕才可以启动后续任务。任务在启动前申请内存,执行完毕后释放,内存释放后可用于其他任务使用。

解除依赖后的任务会直接由操作系统调度,分配内存,进入运行状态。每个任务的运行时间相等。请计算系统所有任务执行所需要的最小内存。  

输入

1行为1个正整数n,表示任务个数,n<20

2行为n个正整数,表示每个任务所需要的内存大小,0<内存<10000

3行为n个取值为0或1的数,表示任务0对其他任务的依赖关系,0表示不依赖,1表示依赖

....

3+n行为n个取值为01的数,表示任务n1对其他任务的依赖关系,0表示不依赖,1表示依赖

输出

输出系统任务执行所需要的最小内存

样例

输入

9
50 50 80 40 40 40 60 60 60
0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0
0 0 1 0 0 1 0 0 0
0 0 0 1 0 0 0 0 0
0 1 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0 
0 0 0 1 0 0 0 0 0
0 0 0 0 0 0 1 1 0

输出

120

解释

样例解释

第一行:9,表示有 9 个任务

第二行:50 50 80 40 40 40 60 60 60,表示每个任务所需要的内存大小

  • t0 需要 50
  • t1 需要 50
  • t2 需要 80
  • t3 需要 40
  • t4 需要 40
  • t5 需要 40
  • t6 需要 60
  • t7 需要 60
  • t8 需要 60

第三行:0 0 0 0 0 0 0 0 0,表示 t0 不依赖任何任务

第四行:1 0 0 0 0 0 0 0 0,表示 t1 依赖 t0

第五行:0 1 0 0 0 0 0 0 0,表示 t2 依赖 t1

...依此类推

任务的关系用图表示:

 

  • 执行 t0,分配 m0 = 50,占用空间 [0, 50),最大访问地址为 50
  • 执行 t1,分配 m1 = 50,占用空间 [0, 50),最大访问地址为 50
  • 并发执行 t2t5,分配 m2 = 80m5 = 40,占用空间 [0, 120),最大访问地址为 120
  • 执行 t3,分配 m3 = 40,占用空间 [0, 40),最大访问地址为 40
  • 并发执行 t4t7,分配 m4 = 40m7 = 60,占用空间 [0, 100),最大访问地址为 100
  • 执行 t6,分配 m6 = 60,占用空间 [0, 60),最大访问地址为 60
  • 执行 t8,分配 m8 = 60,占用空间 [0, 60),最大访问地址为 60

输出系统需要的最小内存为 120

 

思路:拓扑排序+贪心

通过拓扑排序,我们可以确保每个任务按其依赖关系的顺序执行。每一轮计算并行执行的任务所需的总内存,并取这些总内存的最大值作为最终答案。这个方法能有效地解决问题并保证计算出的内存需求最小化。

 

import java.util.*;

public class Main {
    // 定义常量和变量
    static final int M = 20;  // 最大任务数量
    static int[] in = new int[M];  // 记录每个任务的入度
    static int[] a = new int[M];   // 记录每个任务所需的内存

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();  // 读取任务数
        ArrayList<Integer>[] e = new ArrayList[M];  // 邻接表,存储依赖关系

        // 读取每个任务的内存需求并初始化邻接表
        for (int i = 1; i <= n; i++) {
            a[i] = scanner.nextInt();
            e[i] = new ArrayList<>();
        }

        // 读取依赖关系矩阵并构建图
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                int t = scanner.nextInt();
                if (t != 0) {
                    in[i]++;  // 增加任务i的入度
                    e[j].add(i);  // 添加一条从j到i的边
                }
            }
        }

        // 初始化队列,用于拓扑排序
        Queue<Integer> q = new LinkedList<>();
        Queue<Integer> tmp = new LinkedList<>();  // 临时队列,用于保存本轮入度为0的任务
        for (int i = 1; i <= n; i++) {
            if (in[i] == 0) {
                q.add(i);  // 将初始入度为0的任务加入队列
            }
        }

        int ans = 0;  // 保存最终的最小内存需求
        // 开始拓扑排序
        while (!q.isEmpty()) {
            int sum = 0;  // 记录当前轮次所有任务的内存总和
            while (!q.isEmpty()) {
                int u = q.poll();
                sum += a[u];  // 累加当前任务的内存需求
                // 遍历当前任务的所有后继任务
                for (int i = 0; i < e[u].size(); i++) {
                    int v = e[u].get(i);
                    in[v]--;  // 将后继任务的入度减1
                    if (in[v] == 0) {
                        tmp.add(v);  // 如果后继任务的入度为0,加入临时队列
                    }
                }
            }
            ans = Math.max(ans, sum);  // 更新最大内存需求
            q.addAll(tmp);  // 将临时队列中的任务加入主队列,进行下一轮处理
            tmp.clear();  // 清空临时队列
        }

        // 输出最终计算出的最小内存需求
        System.out.println(ans);
    }
}

 

1.3 Dijkstra

求解从某个起点到达图上任意一点的最短路径(BFS 只能求解边权值为1)
import java.util.*;
import java.io.*;

class Main {
    static int N = 100010;  // 定义常量,最大点数

    static int n, m, idx;  // n为点数,m为边数,idx为边的编号
    static int[] h = new int[N];  // h数组,邻接表头结点
    static int[] w = new int[N];  // w数组,边权重
    static int[] e = new int[N];  // e数组,边指向的节点
    static int[] ne = new int[N];  // ne数组,邻接表下一个节点
    static int[] dist = new int[N];  // dist数组,记录1号点到各点的最短距离
    static boolean[] st = new boolean[N];  // st数组,记录每个点是否已确定最短路

    // 添加边的函数
    private static void add(int a, int b, int c) {
        e[idx] = b;  // 边的终点
        w[idx] = c;  // 边的权重
        ne[idx] = h[a];  // 当前边的下一个边
        h[a] = idx++;  // 更新头结点
    }

    public static void main(String[] args) throws Exception {
        // 使用BufferedReader和BufferedWriter进行输入输出
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter log = new BufferedWriter(new OutputStreamWriter(System.out));
        
        // 读取第一个输入行
        String[] values = br.readLine().split("\\s+");
        n = Integer.parseInt(values[0]);
        m = Integer.parseInt(values[1]);
        
        // 初始化邻接表头结点
        Arrays.fill(h, -1);

        // 读取所有边的信息
        while (m-- > 0) {
            values = br.readLine().split("\\s+");
            int a = Integer.parseInt(values[0]);
            int b = Integer.parseInt(values[1]);
            int c = Integer.parseInt(values[2]);
            add(a, b, c);
        }

        // 计算1号点到n号点的最短路径
        int ret = dijkstra(n);

        // 输出结果
        log.write(ret + "\n");
        log.flush();
        log.close();
        br.close();
    }

    // 堆优化的Dijkstra算法
    private static int dijkstra(int n) {
        // 优先队列,按照到达节点的最短距离排序
        PriorityQueue<int[]> pq = new PriorityQueue<>(n, (a, b) -> a[1] - b[1]);
        int INF = 1 << 30;  // 定义一个无限大值
        Arrays.fill(dist, INF);  // 初始化距离数组,全部设为无穷大
        pq.offer(new int[]{1, 0});  // 将起点1加入优先队列
        dist[1] = 0;  // 起点到自己的距离为0

        // 主循环
        while (!pq.isEmpty()) {
            int[] cur = pq.poll();
            int ver = cur[0];  // 当前处理的节点
            int distance = cur[1];  // 当前节点的最短距离

            // 如果该节点已被处理,则跳过
            if (st[ver]) continue;

            st[ver] = true;  // 标记该节点为已处理

            // 遍历当前节点的所有邻边
            for (int i = h[ver]; i != -1; i = ne[i]) {
                int j = e[i];  // 目标节点
                // 如果找到更短的路径,则更新
                if (dist[j] > distance + w[i]) {
                    dist[j] = distance + w[i];
                    pq.offer(new int[]{j, dist[j]});  // 将更新后的节点加入优先队列
                }
            }
        }

        // 如果n号点的最短路径仍为无穷大,说明不可达
        return dist[n] == INF ? -1 : dist[n];
    }
}

  

 

华为2023082303

有一些镜子,能够吸收光芒并在一定时间后散射。

镜子分为一级镜和二级镜,一级镜散射速度为1ms,二级镜为2ms。

将镜子放在一个二维矩阵中,每个镜子的坐标为整数。

现在给某个镜子一道光芒,最早什么时候所有镜子都能吸收到光芒?

注:矩阵的下标从0开始。

 

输入描述:

 

矩阵的列数  n ( n≤500 )

矩阵的行数  m  ( m ≤ 500 )

最初获得光芒的镜子的坐标  (i, j) 

接下来  m  行,每行  n  个数字,代表该位置镜子的等级:

  • 0 表示该位置是一堵密不透光的墙
  • 1 表示该位置的镜子散射耗时1ms
  • 2 表示该位置的镜子散射耗时2ms

 

输出描述:

一个数字代表最小时间。如果有镜子不能够吸收到光芒,输出-1。

 

样例:

输入:

5

5

2 2

1 0 2 1 0

2 2 1 2 0

0 0 1 0 0

2 1 1 0 0

1 1 1 1 1

  

输出:

6

 

思路:

找到所有镜子接收到光芒的最早时间=>单源最短路算法:Dijkstra:

  1. 每一步,从队列中取出时间最早的镜子。
  2. 更新其四个方向镜子的接收时间。
  3. 如果新的接收时间比当前的接收时间早,则更新接收时间,并将新的镜子加入队列。
import java.util.*;

public class Main {

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        // 读取输入,直到没有更多的整数输入
        while (in.hasNextInt()) {
            int n = in.nextInt(); // 矩阵的行数
            int m = in.nextInt(); // 矩阵的列数

            int[][] mirrors = new int[n][m]; // 存储镜子等级的矩阵
            int initI = in.nextInt(); // 初始光芒镜子的行坐标
            int initJ = in.nextInt(); // 初始光芒镜子的列坐标

            // 读取矩阵中的镜子等级
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    mirrors[i][j] = in.nextInt();
                }
            }

            int[][] times = new int[n][m]; // 存储每个镜子接收到光芒的时间
            // 初始化时间矩阵,初始值设为一个较大的数
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    times[i][j] = Integer.MAX_VALUE;
                }
            }

            // 优先队列,用于进行Dijkstra算法
            Queue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>() {
                @Override
                public int compare(int[] o1, int[] o2) {
                    return o1[2] - o2[2]; // 按时间排序
                }
            });

            int[][] visited = new int[n][m]; // 标记每个镜子是否已经接收到光芒
            queue.offer(new int[]{initI, initJ, 0}); // 将初始镜子加入队列,时间设为0

            // Dijkstra算法
            while (!queue.isEmpty()) {
                int[] poll = queue.poll(); // 取出时间最早的镜子
                int x = poll[0];
                int y = poll[1];
                int time = poll[2];

                // 边界条件检查
                if (x < 0 || x >= mirrors.length || y < 0 || y >= mirrors[0].length) {
                    continue;
                }
                // 如果是墙则跳过
                if (mirrors[x][y] == 0) {
                    continue;
                }
                // 如果已经访问过则跳过
                if (visited[x][y] == 1) {
                    continue;
                }

                visited[x][y] = 1; // 标记为已访问
                times[x][y] = time; // 更新当前镜子的时间

                // 将四个方向的镜子加入队列,并更新时间
                queue.offer(new int[]{x - 1, y, time + mirrors[x][y]});
                queue.offer(new int[]{x + 1, y, time + mirrors[x][y]});
                queue.offer(new int[]{x, y - 1, time + mirrors[x][y]});
                queue.offer(new int[]{x, y + 1, time + mirrors[x][y]});
            }

            int max = 0; // 用于找出接收到光芒的最晚时间
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    // 只考虑不是墙的镜子
                    if (mirrors[i][j] != 0) {
                        max = Math.max(max, times[i][j]);
                    }
                }
            }

            // 如果有镜子不能接收到光芒,则返回-1
            if (max == Integer.MAX_VALUE) {
                System.out.println(-1);
            } else {
                System.out.println(max); // 输出最早时间
            }
        }
    }
}

 

饿了么2023081703

有一个迷宫,有 `n` 个地点,通过 `m` 条道路联通。每次需要从起点(1号节点)取一面旗帜送到指定地点,然后返回起点,直到将所有旗帜送完。

你需要知道送完所有旗帜的最短路程是多少。

输入描述

  1. 第一行输入三个整数 `n`, `m`, `q`,分别表示地点数、路径数和需要送达旗帜的地点数。
  2. 接下来 `m` 行,每行输入三个整数 `u`, `v`, `w`,表示地点 `u` 和地点 `v` 之间有一条长度为 `w` 的道路。
  3. 最后一行输入 `q` 个整数,表示需要送达旗帜的 `q` 个地点。

输出描述
一个整数表示最短总路程。

示例
输入

4 3 3
1 2 1
2 3 2
3 4 3
2 3 4

  

 输出

20

  

说明
从 1 号点到 2 号点再回来,路程距离为 2。
再从 1 号点到 3 号点再回来,路程距离为 6。
最后从 1 号点到 4 号点再回来,路程距离为 12。

 思路:

利用Dijkstra,初始化从起点到其余n-1个节点最短路径dist,最后遍历并累加 dist[qi]*2即可。

  

import java.util.*;

public class Main {
    public static void main(String[] args) {
        final int INF = 0x3f3f3f3f; // 定义无穷大
        Scanner scanner = new Scanner(System.in);
        
        int n = scanner.nextInt(); // 地点数
        int m = scanner.nextInt(); // 路径数
        int T = scanner.nextInt(); // 需要送达旗帜的地点数

        List<List<PII>> g = new ArrayList<>(); // 图的邻接表表示
        for (int i = 0; i < n; i++) {
            g.add(new ArrayList<>());
        }

        // 读取图的边
        for (int i = 0; i < m; i++) {
            int u = scanner.nextInt() - 1; // 起点
            int v = scanner.nextInt() - 1; // 终点
            int w = scanner.nextInt();     // 边的权重
            g.get(u).add(new PII(v, w));
            g.get(v).add(new PII(u, w));
        }

        // 优先队列,用于Dijkstra算法
        PriorityQueue<PII> q = new PriorityQueue<>(Comparator.comparingInt(PII::getFirst));

        int[] st = new int[n]; // 标记数组,记录每个节点是否已确定最短路径
        int[] dist = new int[n]; // 最短路径数组
        Arrays.fill(dist, INF); // 初始化最短路径为无穷大
        dist[0] = 0; // 起点的最短路径为0

        q.offer(new PII(0, 0)); // 将起点加入队列
        while (!q.isEmpty()) {
            PII top = q.poll(); // 取出优先队列中的最小元素
            int u = top.getSecond(); // 当前节点
            if (st[u] == 1) continue; // 如果该节点已经确定最短路径,则跳过
            st[u] = 1; // 标记该节点

            for (PII p : g.get(u)) { // 遍历邻接节点
                int v = p.getFirst(); // 邻接节点
                int w = p.getSecond(); // 边的权重
                if (dist[v] > dist[u] + w) { // 更新最短路径
                    dist[v] = dist[u] + w;
                    q.offer(new PII(dist[v], v)); // 将更新后的节点加入队列
                }
            }
        }

        long ans = 0; // 记录总的最短路程
        while (T-- > 0) { // 读取每个需要送达旗帜的地点
            int x = scanner.nextInt() - 1;
            ans += dist[x]; // 累加从起点到该地点的最短路径
        }
        System.out.println(ans * 2); // 输出总的往返路程
    }

    // 辅助类,存储节点及其对应的权重
    static class PII {
        private final int first;
        private final int second;

        public PII(int first, int second) {
            this.first = first;
            this.second = second;
        }

        public int getFirst() {
            return first;
        }

        public int getSecond() {
            return second;
        }
    }
}

  

  

  

 

posted @ 2024-06-04 21:13  菠萝包与冰美式  阅读(3)  评论(0编辑  收藏  举报