#! /usr/bin/env python3 # OBI 2023 - Fase 2 # Barcos - Solução com LCA + MST # Mateus Bezrutchka # Interpretamos o arquipélago como um grafo. # -- Solução para árvores (subtarefas 2 e 3) -- # - No caso em que o grafo é uma linha (segunda subtarefa), o problema é # equivalente a calcular RMQ (Range Minimum Query) em um vetor, o que é # um problema clássico que pode ser feito em O(log N) por query. # - No caso em que o grafo é uma árvore (terceira subtarefa), podemos # usar o algoritmo LCA (Lowest Common Ancestor) para de novo obter O(log N) # por query: o caminho A -> B em uma árvore pode ser decomposto em A -> LCA # e LCA -> B, e os mínimos de cada parte podem ser computados se, além do # vértice, guardarmos na DP do LCA o RMQ do caminho. # -- Solução para só uma consulta (subtarefa 4) -- # Vamos resolver o caso em que só precisamos responder uma única consulta. # Chamamos de MST uma Maximum Spanning Tree (ou Árvore Geradora Máxima) do # grafo -- ou seja, uma árvore com todos os vértices do grafo original e de # máximo custo total (ou, no caso desse problema, capacidade). Podemos provar # -- usando o mesmo argumento de substituição ("cut-and-paste") da prova do # algoritmo de Prim -- que é suficiente só considerar caminhos na MST: se o # caminho de capacidade máxima não estivesse na MST, poderiamos aumentar a # capacidade da MST usando ele (contradição). # Encontrar uma Minimum Spanning Tree do grafo é um problema tradicional, e # é fácil adaptar algoritmos clássicos (aqui usamos Prim) para nosso caso de # custo máximo. Portanto, podemos resolver essa subtarefa encontrando uma MST # e responder a consulta calculando em O(N) o mínimo do caminho X -> Y na MST. # -- Solução completa (subtarefa 5) -- # Para resolver o problema geral, perceba que só precisamos combinar essas duas # soluções: preprocessamos encontrando a MST com Prim e, então, usamos LCA com # RMQ nessa árvore para responder cada consulta em O(log N). # Complexidade: O((B + C) log N). import heapq import math INF = int(1e9 + 1) ## Le entrada def read_int(): return int(input()) def read_int_list(): return [int(x) for x in input().split()] n, b = read_int_list() graph = [[] for v in range(n + 1)] # lista de adjacencias (vertice, capacidade) for i in range(b): x, y, cap = read_int_list() graph[x].append((y, cap)) graph[y].append((x, cap)) ## MST max_cap = [-INF for i in range(n + 1)] parent_cap = [0 for i in range(n + 1)] parent = [v for v in range(n + 1)] depth = [0 for i in range(n + 1)] processed = [False for i in range(n + 1)] def prim(): # inicializa heap para origem 1 max_cap[1] = 0 active_nodes = [(0, 1)] heapq.heapify(active_nodes) while len(active_nodes) > 0: cur = heapq.heappop(active_nodes) v = cur[1] if processed[v]: continue processed[v] = True parent_cap[v] = -cur[0] for w, cap in graph[v]: if processed[w]: continue # modificado para pegar arestas de maior capacidade if max_cap[w] < cap: max_cap[w] = cap parent[w] = v depth[w] = depth[v] + 1 heapq.heappush(active_nodes, (-max_cap[w], w)) prim() ## LCA logn = math.ceil(math.log(n, 2) + 1) # dp[k][i] guarda o par (k-esimo ancestral de i, menor cap no caminho) dp = [[0 for i in range(n + 1)] for k in range(logn + 1)] def build_lca_dp(): dp[0] = [(parent[v], parent_cap[v]) for v in range(n + 1)] dp[0][1] = (1, INF) for k in range(1, logn): for v in range(1, n + 1): w = dp[k - 1][v][0] v_cap = dp[k - 1][v][1] w_cap = dp[k - 1][w][1] dp[k][v] = (dp[k - 1][w][0], min(v_cap, w_cap)) build_lca_dp() def find_min_cap(a, b): if depth[a] < depth[b]: a, b = b, a ans = INF for k in reversed(range(logn)): nxt_a = dp[k][a][0] if depth[nxt_a] >= depth[b]: ans = min(ans, dp[k][a][1]) a = nxt_a if a == b: # b eh ancestral de a return ans for k in reversed(range(logn)): nxt_a, nxt_b = dp[k][a][0], dp[k][b][0] if nxt_a != nxt_b: ans = min([ans, dp[k][a][1], dp[k][b][1]]) a, b = nxt_a, nxt_b # a essa altura, dp[0] guarda o LCA ans = min([ans, dp[0][a][1], dp[0][b][1]]) return ans # Responde consultas c = read_int() for i in range(c): x, y = read_int_list() print(find_min_cap(x, y))