leetcode
leetcode 3001 ~ 3050
夺回据点

夺回据点

难度:

标签:

题目描述

English description is not available for the problem. Please switch to Chinese.

代码结果

运行时间: 577 ms, 内存: 67.6 MB


/*
 * 思路:
 * 1. 找出最低成本的据点作为初始夺回的据点。
 * 2. 使用一个并查集(Union-Find)结构来维护已夺回和未夺回据点的连通性。
 * 3. 从最小花费开始尝试夺回据点,确保每次夺回据点后,剩余的魔物据点依然连通。
 */

import java.util.*;
import java.util.stream.Collectors;

public class Solution {
    public int minCost(int[] cost, int[][] roads) {
        int n = cost.length;
        UnionFind uf = new UnionFind(n);
        List<int[]> edges = Arrays.stream(roads)
                                   .sorted(Comparator.comparingInt(o -> Math.min(cost[o[0]], cost[o[1]])))
                                   .collect(Collectors.toList());
        int minCost = Integer.MAX_VALUE;
        for (int[] edge : edges) {
            int u = edge[0];
            int v = edge[1];
            if (!uf.connected(u, v)) {
                uf.union(u, v);
                minCost = Math.min(minCost, cost[u] + cost[v]);
            }
        }
        return minCost;
    }

    class UnionFind {
        private int[] parent;
        private int[] rank;

        public UnionFind(int n) {
            parent = new int[n];
            rank = new int[n];
            for (int i = 0; i < n; i++) {
                parent[i] = i;
                rank[i] = 1;
            }
        }

        public int find(int x) {
            if (x != parent[x]) {
                parent[x] = find(parent[x]);
            }
            return parent[x];
        }

        public void union(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);
            if (rootX != rootY) {
                if (rank[rootX] > rank[rootY]) {
                    parent[rootY] = rootX;
                } else if (rank[rootX] < rank[rootY]) {
                    parent[rootX] = rootY;
                } else {
                    parent[rootY] = rootX;
                    rank[rootX]++;
                }
            }
        }

        public boolean connected(int x, int y) {
            return find(x) == find(y);
        }
    }
}

解释

方法:

本题解采用了基于深度优先搜索(DFS)的点双连通分量(Biconnected Components)的算法来解决问题。每个点双连通分量是一个最大的子图,其中任何两个顶点都有两条不相交的路径相连,这样即使去掉任何一个节点,其余节点依然连通。算法的主要步骤如下:1. 使用DFS遍历图,同时记录每个节点的访问序号dfn和最小可回溯到的祖先节点序号low。2. 利用dfn和low值,判断并记录割点(关键节点,其移除会导致图的不连通)。3. 构建每个点双连通分量,每遇到一个新的割点或遍历完一个连通分量时,将该分量的节点记录下来。4. 对每个点双连通分量,找到非割点的最小成本,以此计算最小的资源消耗。5. 特殊处理单个割点和非割点情况下的最小成本计算。整体思路是通过图的深度优先遍历确定关键节点和连通分量,然后基于每个连通分量的成本最小化总资源消耗。

时间复杂度:

O(V + E)

空间复杂度:

O(V + E)

代码细节讲解

🦆
在DFS过程中,`dfn[u] <= low[v]`条件是如何确定节点u是否为割点的?请解释其背后的逻辑。
在深度优先搜索(DFS)中,`dfn[u]`表示节点u被访问的时间戳,而`low[v]`表示从节点v出发,通过一条或多条后向边能够到达的最早被访问的节点的时间戳。如果`dfn[u] <= low[v]`成立,说明从u到v的子树中没有后向边指向u的祖先,即u是v的子树和其它部分之间的桥梁。如果去掉u,v的子树将与图的其它部分断开,因此u是一个割点。特别地,对于根节点,如果它有两个以上的子节点满足该条件,也将其视为割点。
🦆
在处理点双连通分量时,为什么只考虑非割点的最小成本?割点的成本是否应该也考虑在内?
在处理点双连通分量时,只考虑非割点的最小成本是因为割点可能属于多个点双连通分量,因此它的成本可能会被重复计算。为了避免重复计算和可能导致的过高成本,算法只从每个分量的非割点中选择最小成本。这样,每个分量的最小成本可以给出维持该分量连通性所需的最小资源。割点的成本在最终成本计算中将按需考虑,以确保所有连接的最优性。
🦆
算法中提到,如果一个点双连通分量中只有一个割点,就使用那个分量的最小成本。这种方法是否能确保总成本最小?存在没有割点的分量该如何处理?
如果一个点双连通分量中只有一个割点,使用该分量的最小成本是为了确保在不影响整体连通性的前提下,尽可能减少该分量的资源消耗。这种方法在大多数情况下可以帮助接近总成本的最小化,但不一定总是最优,因为它可能没有考虑割点在多个分量中的共享成本影响。对于没有割点的分量,即该分量内部完全连通且独立于其他部分,应计算该分量所有非割点的成本总和,因为这些点都是必需的,以保持分量的内部连通性。

相关问题