难度: Hard
Submission
运行时间: 577 ms
内存: 67.6 MB
# vbcc 点双连通 class Solution: def minimumCost(self, c: List[int], e: List[List[int]]) -> int: n = len(c) g = [[] for _ in range(n)] for x, y in e: g[x].append(y) g[y].append(x) t = 0 dfn = [0]*n low = [0]*n cut = [0]*n root = 0 vbcc = defaultdict(list) cnt = 0 stack = [] def dfs(u, fa): nonlocal t t += 1 dfn[u] = t low[u] = t flag = 0 stack.append(u) for v in g[u]: if not dfn[v]: dfs(v, u) low[u] = min(low[u], low[v]) if dfn[u]<=low[v]: flag += 1 if (u!=root) or (flag>1): cut[u] = 1 nonlocal cnt vbcc[cnt].append(u) while 1: z = stack.pop() vbcc[cnt].append(z) if z==v: break cnt += 1 else: low[u] = min(low[u], dfn[v]) dfs(0, -1) # dfs(0, 0) ans = [] for j in vbcc: num = 0 tmp = [] for k in vbcc[j]: if cut[k]==0: tmp.append(c[k]) if cut[k]: num += 1 if num==1: ans.append(min(tmp)) # print(ans) if not ans: return min(c) if len(ans)==1: return ans[0] else: ans.sort() return sum(ans[:-1])
Explain
本题解采用了基于深度优先搜索(DFS)的点双连通分量(Biconnected Components)的算法来解决问题。每个点双连通分量是一个最大的子图,其中任何两个顶点都有两条不相交的路径相连,这样即使去掉任何一个节点,其余节点依然连通。算法的主要步骤如下:1. 使用DFS遍历图,同时记录每个节点的访问序号dfn和最小可回溯到的祖先节点序号low。2. 利用dfn和low值,判断并记录割点(关键节点,其移除会导致图的不连通)。3. 构建每个点双连通分量,每遇到一个新的割点或遍历完一个连通分量时,将该分量的节点记录下来。4. 对每个点双连通分量,找到非割点的最小成本,以此计算最小的资源消耗。5. 特殊处理单个割点和非割点情况下的最小成本计算。整体思路是通过图的深度优先遍历确定关键节点和连通分量,然后基于每个连通分量的成本最小化总资源消耗。
时间复杂度: O(V + E)
空间复杂度: O(V + E)
# vbcc 点双连通 class Solution: def minimumCost(self, c: List[int], e: List[List[int]]) -> int: n = len(c) # 节点数量 g = [[] for _ in range(n)] # 邻接表 for x, y in e: # 构建无向图 g[x].append(y) g[y].append(x) t = 0 # 时间戳 dfn = [0]*n # 节点u的访问时间戳 low = [0]*n # 节点u通过反向边能访问到的最早访问节点的时间戳 cut = [0]*n # 标记是否为割点 root = 0 # 根节点 vbcc = defaultdict(list) # 存储点双连通分量 cnt = 0 # 点双连通分量的计数 stack = [] # 栈用于存储路径 def dfs(u, fa): # 深度优先搜索 nonlocal t t += 1 dfn[u] = t low[u] = t flag = 0 stack.append(u) for v in g[u]: if not dfn[v]: # 如果v未被访问过 dfs(v, u) low[u] = min(low[u], low[v]) if dfn[u]<=low[v]: # 判断u是否为割点 flag += 1 if (u!=root) or (flag>1): cut[u] = 1 nonlocal cnt vbcc[cnt].append(u) while 1: z = stack.pop() vbcc[cnt].append(z) if z==v: break cnt += 1 else: low[u] = min(low[u], dfn[v]) dfs(0, -1) # 从节点0开始DFS ans = [] for j in vbcc: # 处理每个点双连通分量 num = 0 tmp = [] for k in vbcc[j]: if cut[k]==0: # 如果不是割点 tmp.append(c[k]) if cut[k]: num += 1 if num==1: # 只有一个割点 ans.append(min(tmp)) if not ans: return min(c) if len(ans)==1: return ans[0] else: ans.sort() return sum(ans[:-1])
Explore
在深度优先搜索(DFS)中,`dfn[u]`表示节点u被访问的时间戳,而`low[v]`表示从节点v出发,通过一条或多条后向边能够到达的最早被访问的节点的时间戳。如果`dfn[u] <= low[v]`成立,说明从u到v的子树中没有后向边指向u的祖先,即u是v的子树和其它部分之间的桥梁。如果去掉u,v的子树将与图的其它部分断开,因此u是一个割点。特别地,对于根节点,如果它有两个以上的子节点满足该条件,也将其视为割点。
在处理点双连通分量时,只考虑非割点的最小成本是因为割点可能属于多个点双连通分量,因此它的成本可能会被重复计算。为了避免重复计算和可能导致的过高成本,算法只从每个分量的非割点中选择最小成本。这样,每个分量的最小成本可以给出维持该分量连通性所需的最小资源。割点的成本在最终成本计算中将按需考虑,以确保所有连接的最优性。
如果一个点双连通分量中只有一个割点,使用该分量的最小成本是为了确保在不影响整体连通性的前提下,尽可能减少该分量的资源消耗。这种方法在大多数情况下可以帮助接近总成本的最小化,但不一定总是最优,因为它可能没有考虑割点在多个分量中的共享成本影响。对于没有割点的分量,即该分量内部完全连通且独立于其他部分,应计算该分量所有非割点的成本总和,因为这些点都是必需的,以保持分量的内部连通性。