20221126 - 882. Reachable Nodes In Subdivided Graph 细分图中的可到达节点(最短路径)

79 阅读2分钟

882. Reachable Nodes In Subdivided Graph

You are given an undirected graph (the  "original graph" ) with n nodes labeled from 0 to n - 1. You decide to subdivide each edge in the graph into a chain of nodes, with the number of new nodes varying between each edge.

The graph is given as a 2D array of edges where edges[i] = [ui, vi, cnti] indicates that there is an edge between nodes ui and vi in the original graph, and cnti is the total number of new nodes that you will subdivide the edge into. Note that cnti == 0 means you will not subdivide the edge.

To subdivide the edge [ui, vi], replace it with (cnti + 1) new edges and cnti new nodes. The new nodes are x1x2, ..., xcnti, and the new edges are [ui, x1][x1, x2][x2, x3], ..., [xcnti-1, xcnti][xcnti, vi].

In this new graph, you want to know how many nodes are reachable from the node 0, where a node is reachable if the distance is maxMoves or less.

Given the original graph and maxMoves, return the number of nodes that are reachable from node 0 in the new graph.

 

Example 1:

Input: edges = [[0,1,10],[0,2,1],[1,2,2]], maxMoves = 6, n = 3
Output: 13
Explanation: The edge subdivisions are shown in the image above.
The nodes that are reachable are highlighted in yellow.

Example 2:

Input: edges = [[0,1,4],[1,2,6],[0,2,8],[1,3,1]], maxMoves = 10, n = 4
Output: 23

Example 3:

Input: edges = [[1,2,4],[1,4,5],[1,3,1],[2,3,4],[3,4,5]], maxMoves = 17, n = 5
Output: 1
Explanation: Node 0 is disconnected from the rest of the graph, so only node 0 is reachable.

 

Constraints:

  • 0 <= edges.length <= min(n * (n - 1) / 2, 104)

  • edges[i].length == 3

  • 0 <= ui < vi < n

  • There are no multiple edges in the graph.

  • 0 <= cnti <= 104

  • 0 <= maxMoves <= 109

  • 1 <= n <= 3000

Solution:

首先我们将原始图的节点定义为“大节点”,细分图中新增的节点定义为“小节点”。

那么本题的核心思路可以总结为:计算到达每个大节点后,剩余步数可以到达的小节点个数。

这个思路又可以拆解成为两部分,一是求解到达每个大节点时剩余的步数,二是如何计算剩余步数可以到达的小节点数。

首先解释第一部分,这一部分使用了Dijkstra算法。这是一种计算某个点到达其余所有点的最短路径的算法。这个算法并不难理解,我在此拿题目的示例二来做个说明。

在说明前,我们要先明白,在细分图中,大节点之间的距离实际上就是小节点的个数+1,也就是细分图中“新边”的数量。所以0节点到各个大节点的距离就是0(0),1(5),2(9),3(∞),括号中的数字代表距离,括号前的数字代表大节点的标号。只要是没有直接相连的两个大节点,在刚开始的距离都先视为∞。

然后我们需要维护两个容器,不管是链表还是数组都无所谓。一个是result,表示已经求出的最短路径,另一个是notfound,表示尚未求出最短路径。我们需要不断在notfound中找出最短的边,并以边另一端的节点为新的起点更新notfound。

开始时notfound:0(0),1(5),2(9),3(∞),所有边全部在notfound中。 首先选出0(0)进入result,该边另一端依然是0节点,所以没有影响。 此时,notfound:1(5),2(9),3(∞),result:0(0),然后选出1(5)进入result,并以1(5)为新节点更新notfound。 与1节点相连的边有:0(5),1(0),2(7),3(2),全部加上0节点到1节点的距离,就是0(10),1(5),2(12),3(7),与notfound中剩余的边比较,取最小值后,notfound更新为2(9),3(7)。 然后取出3(7)进入result,并重复之前的过程。

当所有边全部进入result后,其结果便是0节点到各个大节点的最短路径。这便是Dijkstra算法。

求出最短路径后,便能求出到达每个大节点后的剩余步数maxMoves-steps[x],此处使用steps[x]表示到x节点的最短路径。

计算每个大节点需要到达的小节点,我们只需要考虑与大节点直接相连的边上的小节点,其他边上的小节点自有其他大节点来计算。

x节点可以到达的小节点便是sum(sum(min(maxMoves-steps[x],nodes[i]))),nodes[i]表示和x节点相连的第i条边上的小节点数。min(maxMoves-steps[x],nodes[i])表示取剩余步数和边上小节点数的最小值,至于两个sum,最外层的sum遍历x,将所有大节点相加,里面的sum则是遍历i,将所有和x节点相连的边上可以到达的小节点加起来。

这样计算绝对不会出现疏漏,但却有可能重复,因为x节点与y节点之间可能有一条边相连。在x节点可能到达的小节点,在y节点可能也能到达,所以我们还要考虑到去重。

而去重只需要在之前的求和上做些改动,sum(min((maxMoves-steps[x])+(maxMoves-steps[y]),nodes[i]))。我们可以理解为遍历对象从大节点变成了边。之前计算每个大节点所有相连的边上可以到达的小节点后,还要将所有大节点求和,而这样计算则是直接求和所有边上可以到达的小节点。x、y表示第i边的两个端点,即便是两个端点,在同一条边上最多也只能到达nodes[i]个小节点。

最后就是官方题解的代码:

作者:zhishangju 链接:leetcode.cn/problems/re… 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

class Solution {
public:
    int encode(int u, int v, int n) {
        return u * n + v;
    } //记录各条边上的细分节点的可达情况哈希表的哈希函数

    int reachableNodes(vector<vector<int>>& edges, int maxMoves, int n) {
        vector<vector<pair<int, int>>> adList(n); //邻接表存稀疏图
        for (auto &edge : edges) {
            int u = edge[0], v = edge[1], nodes = edge[2]; //同时存两边之间的细分节点个数
            adList[u].emplace_back(v, nodes);
            adList[v].emplace_back(u, nodes); //无向图
        }

        unordered_map<int, int> used; //记录各条边上的细分节点的可达情况哈希表, 键值由哈希函数给出
        unordered_set<int> visited; //已求得最短路径的大节点集合
        int reachableNodes = 0; //可达节点总个数
        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq; //小顶堆
        pq.emplace(0, 0); //源节点入堆
        while (!pq.empty() && pq.top().first <= maxMoves) { //堆不为空 且 堆中最短路径不超过题目限制的最大步数
            auto [step, u] = pq.top(); //取出堆顶元素, 更新路径后未收入集合的大节点中到源点距离最小的
            pq.pop();
            if (visited.count(u)) { //如果节点已收入, 跳过
                continue;
            }
            visited.emplace(u); //否则节点标记为已收入
            reachableNodes++; //可达节点总数加一
            for (auto [v, nodes] : adList[u]) { //遍历当前节点的所有邻接点
                if (nodes + step + 1 <= maxMoves && !visited.count(v)) { //邻接点可达 且 邻接点没有被收入
                    pq.emplace(nodes + step + 1, v); //邻接点入堆
                }
                used[encode(u, v, n)] = min(nodes, maxMoves - step); //记录从u到v方向可达细分节点个数
            }
        }

        for (auto &edge : edges) { //遍历所有的边以计算可达小节点个数
            int u = edge[0], v = edge[1], nodes = edge[2];
            reachableNodes += min(nodes, used[encode(u, v, n)] + used[encode(v, u, n)]);
            //每条边上可达细分节点的个数为从u到v和从v到u之前计算可达细分节点数之和, 最多不超过边上细分节点总数
        }
        return reachableNodes;
    }
};

作者:LeetCode-Solution
链接:https://leetcode.cn/problems/reachable-nodes-in-subdivided-graph/solution/xi-fen-tu-zhong-de-ke-dao-da-jie-dian-by-u8m1/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。