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
- 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.
- 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).
- 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.
- 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.
投稿日時:
最終更新: