公式

E - 妨害と通信ネットワーク / Jamming and Communication Network 解説 by admin

Gemini 3.0 Flash (Thinking)

Overview

This problem asks us to find the maximum weight of a Minimum Spanning Tree (MST) after removing one edge from the graph.

Aoki tries to maximize the total cost of the MST that Takahashi constructs, but removing an edge not in the MST does not change the MST’s weight. Therefore, Aoki will choose to cut one edge from the original MST such that the replacement edge (alternative edge) has the largest possible cost.

Analysis

1. Which edge should be cut?

First, compute the minimum spanning tree \(T\) of the entire graph. - If Aoki cuts an edge not in \(T\): Takahashi can still construct \(T\), so the MST weight does not change. - If Aoki cuts an edge \(e\) in \(T\): Takahashi will choose the edge \(e'\) with the smallest cost among those that reconnect \(T \setminus \{e\}\), and construct a new MST.

Therefore, Aoki’s strategy is to “compute the minimum cost increase for each edge \(e\) in the MST when it is cut, and choose the \(e\) that maximizes this increase.”

2. Conditions for alternative edges

When an edge \(e\) in MST \(T\) is cut, the tree \(T\) splits into two components. An edge \(e' = (u, v)\) that can reconnect them is one where “the path between vertices \(u\) and \(v\) in the original tree \(T\) contains edge \(e\).”

Takahashi will choose the edge \(e'\) with the minimum weight \(W_{e'}\) among such candidates.

3. Efficient computation

Naively searching for alternative edges for every edge \(e \in T\) would take \(O(NM)\) in the worst case, which is too slow. Instead, we use the following property:

  • Consider all non-MST edges \((u, v, w)\). Such an edge serves as an “alternative edge candidate” with weight \(w\) for every edge \(e\) on the path from \(u\) to \(v\) in tree \(T\).
  • For each edge \(e \in T\), we want to know the minimum weight \(w\) among non-MST edges whose path contains \(e\).

This can be solved by “sorting non-MST edges in ascending order of weight and filling in tree path edges that have not yet been assigned an alternative edge.”

Algorithm

  1. Construct the Minimum Spanning Tree (MST): Use Kruskal’s algorithm to find the MST, and let its total weight be \(S\). Let \(E_{MST}\) be the set of edges in the MST and \(E_{other}\) be the set of edges not in the MST.
  2. Organize the tree structure: Treat the MST as a rooted tree, recording each vertex’s depth and the edge to its parent. Also, use Binary Lifting (Doubling) to enable fast computation of the Lowest Common Ancestor (LCA).
  3. Determine alternative edges (path covering): Sort the edges in \(E_{other}\) in ascending order of weight \(W\). For each edge \((u, v, w) \in E_{other}\), assign weight \(w\) to all edges on the path from \(u\) to \(v\) that have not yet been assigned an alternative edge.
    • To speed up the “skipping already assigned edges” operation, use a Union-Find (DSU). By managing “the nearest unprocessed vertex above the current vertex” with DSU for each vertex, we can process each edge exactly once in \(O(M \alpha(N))\) time.
  4. Compute the answer: For each edge \(e \in E_{MST}\), let \(W_{alt}(e)\) be the weight of its alternative edge. The MST weight after cutting that edge is \(S - W_e + W_{alt}(e)\). The maximum value among these and the original \(S\) is the answer.

Complexity

  • MST construction: \(O(M \log M)\) or \(O(M \log N)\)
  • LCA construction: \(O(N \log N)\)
  • Alternative edge computation (sorting + DSU path covering): \(O(M \log M + M \alpha(N))\)
  • Time complexity: \(O(M \log M)\)
  • Space complexity: \(O(N \log N + M)\)

Implementation Notes

  • Recursion limit: In Python, deep tree traversals can easily hit the recursion limit, so it is safer to implement DFS and tree construction using an iterative approach with an explicit stack.

  • Path compression with DSU: When traversing a path upward, previously processed vertices can be skipped by performing dsu.union(current, parent), which dramatically reduces the computation time.

  • 2-edge-connectivity guarantee: The problem guarantees that the graph is 2-edge-connected, so an alternative edge is guaranteed to exist for every \(e \in E_{MST}\).

    Source Code

import sys

# Increase recursion depth for safety, though iterative methods are used
sys.setrecursionlimit(300000)

def solve():
    # Efficiently read input data
    input_data = sys.stdin.read().split()
    if not input_data:
        return
    it = iter(input_data)
    N = int(next(it))
    M = int(next(it))
    
    edges = []
    for i in range(M):
        u = int(next(it))
        v = int(next(it))
        w = int(next(it))
        edges.append((u, v, w, i))
    
    # Kruskal's algorithm to find the Minimum Spanning Tree (MST)
    # Sort edges by weight to build MST
    edges.sort(key=lambda x: x[2])
    parent_mst = list(range(N + 1))
    
    # Iterative DSU find function
    def find(p, i):
        root = i
        while p[root] != root:
            root = p[root]
        while p[i] != root:
            nxt = p[i]
            p[i] = root
            i = nxt
        return root

    mst_weight = 0
    mst_edge_indices = []
    non_mst_edges = []
    adj = [[] for _ in range(N + 1)]
    edge_weights = [0] * M
    
    for u, v, w, i in edges:
        edge_weights[i] = w
        root_u = find(parent_mst, u)
        root_v = find(parent_mst, v)
        if root_u != root_v:
            parent_mst[root_u] = root_v
            mst_weight += w
            mst_edge_indices.append(i)
            adj[u].append((v, i))
            adj[v].append((u, i))
        else:
            # Non-MST edges are candidates for replacing removed MST edges
            non_mst_edges.append((u, v, w, i))
    
    # Root the MST at node 1 and precompute depths and parents using iterative DFS
    depth = [0] * (N + 1)
    parent_node = [0] * (N + 1)
    edge_to_parent_idx = [-1] * (N + 1)
    stack = [1]
    visited = [False] * (N + 1)
    visited[1] = True
    while stack:
        u = stack.pop()
        for v, i in adj[u]:
            if not visited[v]:
                visited[v] = True
                parent_node[v] = u
                edge_to_parent_idx[v] = i
                depth[v] = depth[u] + 1
                stack.append(v)
    
    # Precompute binary lifting table for Lowest Common Ancestor (LCA)
    LOGN = (N).bit_length()
    up = [[0] * (N + 1) for _ in range(LOGN)]
    up[0] = parent_node
    for i in range(1, LOGN):
        up_prev = up[i-1]
        up_curr = up[i]
        for u in range(1, N + 1):
            up_curr[u] = up_prev[up_prev[u]]
    
    def get_lca(u, v):
        if depth[u] < depth[v]:
            u, v = v, u
        diff = depth[u] - depth[v]
        for i in range(LOGN):
            if (diff >> i) & 1:
                u = up[i][u]
        if u == v:
            return u
        for i in range(LOGN - 1, -1, -1):
            up_i = up[i]
            if up_i[u] != up_i[v]:
                u = up_i[u]
                v = up_i[v]
        return up[0][u]
    
    # DSU for path covering to efficiently find the minimum replacement weight for each MST edge
    # dsu_path[i] points to the highest ancestor of node i whose parent-edge hasn't been processed
    dsu_path = list(range(N + 1))
    replacement_weight = [-1] * M
    
    # Process non-MST edges (already sorted by weight) to cover edges on the tree paths
    for u, v, w, i in non_mst_edges:
        lca = get_lca(u, v)
        # Traverse path from u to lca
        curr = find(dsu_path, u)
        while depth[curr] > depth[lca]:
            idx = edge_to_parent_idx[curr]
            if replacement_weight[idx] == -1:
                replacement_weight[idx] = w
            # Use DSU to jump over this edge in future traversals
            dsu_path[curr] = find(dsu_path, parent_node[curr])
            curr = dsu_path[curr]
        # Traverse path from v to lca
        curr = find(dsu_path, v)
        while depth[curr] > depth[lca]:
            idx = edge_to_parent_idx[curr]
            if replacement_weight[idx] == -1:
                replacement_weight[idx] = w
            dsu_path[curr] = find(dsu_path, parent_node[curr])
            curr = dsu_path[curr]
    
    # Aoki wants to maximize the weight of the resulting MST after removing one edge
    # If he removes an edge not in the MST, the MST weight remains mst_weight
    # If he removes an MST edge e, the weight becomes mst_weight - W(e) + W(replacement_e)
    ans = mst_weight
    for i in mst_edge_indices:
        if replacement_weight[i] != -1:
            ans = max(ans, mst_weight - edge_weights[i] + replacement_weight[i])
    
    # Output the maximized MST weight
    sys.stdout.write(str(ans) + '\n')

if __name__ == '__main__':
    solve()

This editorial was generated by gemini-3-flash-thinking.

投稿日時:
最終更新: