#!/usr/bin/env pypy3 from dataclasses import dataclass, field from collections import defaultdict from typing import Optional import heapq @dataclass class Instruction: train: str station: str @dataclass class Path: instruction: Instruction prev: Optional["Path"] @dataclass(order=True) class Scoring: distance: int path: Optional[Path] = field(compare=False) @dataclass class Node: edges: list["Edge"] = field(default_factory=list) scoring: Optional[Scoring] = field(default=None) @dataclass class Edge: length: int target: Node instruction: Optional[Instruction] = field(default=None) MAX_TIME = 24 * 60 * 60 def tokens() -> list[str]: return input().split() def build_graph() -> tuple[Node, list[Node]]: start, start_time = tokens() start_time = int(start_time) (end,) = tokens() (train_count,) = tokens() train_count = int(train_count) stations: defaultdict[str, dict[int, Node]] = defaultdict(dict) for _ in range(train_count): train, stop_count = tokens() stop_count = int(stop_count) prev_node = None for _ in range(stop_count): station_name, time = tokens() time = int(time) station = stations[station_name] node = Node() station[time] = node if prev_node is not None: prev_node.edges.append( Edge(0, node, Instruction(train, station_name)) ) prev_node = node start_node = stations[start].setdefault(start_time, Node()) end_nodes = list(stations[end].values()) for station in stations.values(): station = sorted(station.items()) prev_time, prev = station[-1] for next_time, next in station: wait_time = (next_time - prev_time) % MAX_TIME prev.edges.append(Edge(wait_time, next, None)) prev, prev_time = next, next_time return start_node, end_nodes def mark_shortest_paths(start: Node): @dataclass(order=True) class QueueElement: scoring: Scoring node: Node = field(compare=False) queue: list[QueueElement] = [QueueElement(Scoring(0, None), start)] while len(queue) > 0: el = heapq.heappop(queue) node = el.node if node.scoring is not None: continue node.scoring = el.scoring for edge in node.edges: path = node.scoring.path if edge.instruction is not None: path = Path(edge.instruction, path) scoring = Scoring(node.scoring.distance + edge.length, path) heapq.heappush(queue, QueueElement(scoring, edge.target)) def get_best_end(ends: list[Node]) -> Optional[Scoring]: best_scoring = None for node in ends: if node.scoring is None: continue if best_scoring is None or best_scoring.distance > node.scoring.distance: best_scoring = node.scoring return best_scoring def path_to_instructions(path: Path) -> list[Instruction]: instructions = [] while path is not None: if len(instructions) == 0 or instructions[-1].train != path.instruction.train: instructions.append(path.instruction) path = path.prev instructions.reverse() return instructions def main(): start, ends = build_graph() mark_shortest_paths(start) best_scoring = get_best_end(ends) if best_scoring is None: print("No solution") else: instructions = path_to_instructions(best_scoring.path) for inst in instructions: print(inst.train, inst.station) main()