using System; using System.Collections.Generic; using System.IO; using System.Numerics; using System.Text; public class Program { private const int MaxAb = 300; private struct RawEdge { public int U; public int V; public int Numerator; public int Denominator; public RawEdge(int u, int v, int numerator, int denominator) { U = u; V = v; Numerator = numerator; Denominator = denominator; } } private struct Edge { public int To; public BigInteger Weight; public Edge(int to, BigInteger weight) { To = to; Weight = weight; } } private struct State { public BigInteger Distance; public int Vertex; public State(BigInteger distance, int vertex) { Distance = distance; Vertex = vertex; } } private struct PrimePower { public int Prime; public int Exponent; public PrimePower(int prime, int exponent) { Prime = prime; Exponent = exponent; } } private sealed class MinHeap { private readonly List heap = new List(); public int Count { get { return heap.Count; } } private static bool Less(State lhs, State rhs) { int comparison = lhs.Distance.CompareTo(rhs.Distance); if (comparison != 0) { return comparison < 0; } return lhs.Vertex < rhs.Vertex; } public void Push(State state) { int index = heap.Count; heap.Add(state); while (index > 0) { int parent = (index - 1) / 2; if (!Less(state, heap[parent])) { break; } heap[index] = heap[parent]; index = parent; } heap[index] = state; } public State Pop() { State result = heap[0]; int lastIndex = heap.Count - 1; State last = heap[lastIndex]; heap.RemoveAt(lastIndex); if (heap.Count == 0) { return result; } int index = 0; while (true) { int left = index * 2 + 1; if (left >= heap.Count) { break; } int right = left + 1; int child = left; if (right < heap.Count && Less(heap[right], heap[left])) { child = right; } if (!Less(heap[child], last)) { break; } heap[index] = heap[child]; index = child; } heap[index] = last; return result; } } private sealed class FastScanner { private readonly Stream input = Console.OpenStandardInput(); private readonly byte[] buffer = new byte[1 << 16]; private int length; private int position; private int Read() { if (position >= length) { length = input.Read(buffer, 0, buffer.Length); position = 0; if (length == 0) { return -1; } } return buffer[position++]; } public int NextInt() { int c; do { c = Read(); } while (c <= 32 && c != -1); int sign = 1; if (c == '-') { sign = -1; c = Read(); } int value = 0; while ('0' <= c && c <= '9') { value = value * 10 + c - '0'; c = Read(); } return value * sign; } } public static void Main() { var scanner = new FastScanner(); int n = scanner.NextInt(); int m = scanner.NextInt(); var rawEdges = new List(m); // maximumExponent[p]は、いずれかのb_iに現れる // 素数pの指数の最大値。 var maximumExponent = new int[MaxAb + 1]; for (int i = 0; i < m; ++i) { int u = scanner.NextInt() - 1; int v = scanner.NextInt() - 1; int a = scanner.NextInt(); int b = scanner.NextInt(); rawEdges.Add(new RawEdge(u, v, a, b)); int value = b; for (int prime = 2; prime * prime <= value; ++prime) { if (value % prime != 0) { continue; } int exponent = 0; while (value % prime == 0) { value /= prime; ++exponent; } maximumExponent[prime] = Math.Max(maximumExponent[prime], exponent); } if (value > 1) { maximumExponent[value] = Math.Max(maximumExponent[value], 1); } } // すべてのb_iを割り切る共通分母を構築する。 BigInteger commonDenominator = BigInteger.One; var primePowers = new List(); for (int prime = 2; prime <= MaxAb; ++prime) { int exponent = maximumExponent[prime]; if (exponent == 0) { continue; } primePowers.Add(new PrimePower(prime, exponent)); for (int count = 0; count < exponent; ++count) { commonDenominator *= prime; } } var graph = new List[n]; for (int vertex = 0; vertex < n; ++vertex) { graph[vertex] = new List(); } foreach (RawEdge raw in rawEdges) { BigInteger scaledWeight = commonDenominator / raw.Denominator * raw.Numerator; // BigIntegerは不変の値型なので、同じ値を両方向で使用できる。 graph[raw.U].Add(new Edge(raw.V, scaledWeight)); graph[raw.V].Add(new Edge(raw.U, scaledWeight)); } var distance = new BigInteger[n]; var reached = new bool[n]; var queue = new MinHeap(); reached[0] = true; distance[0] = BigInteger.Zero; queue.Push(new State(BigInteger.Zero, 0)); while (queue.Count > 0) { State current = queue.Pop(); if (!reached[current.Vertex] || current.Distance != distance[current.Vertex]) { continue; } foreach (Edge edge in graph[current.Vertex]) { // 任意精度整数の加算を一度だけ行う。 BigInteger nextDistance = current.Distance + edge.Weight; if (!reached[edge.To] || nextDistance < distance[edge.To]) { reached[edge.To] = true; distance[edge.To] = nextDistance; queue.Push(new State(nextDistance, edge.To)); } } } var output = new StringBuilder(8 * 1024 * 1024); for (int vertex = 1; vertex < n; ++vertex) { BigInteger numerator = distance[vertex]; BigInteger denominator = commonDenominator; // 分母の素因数分解は既知なので、 // 各素数で割れる回数だけ約分する。 foreach (PrimePower primePower in primePowers) { int prime = primePower.Prime; int exponent = primePower.Exponent; for (int count = 0; count < exponent; ++count) { if (numerator % prime != BigInteger.Zero) { break; } numerator /= prime; denominator /= prime; } } output.Append(numerator); output.Append(' '); output.Append(denominator); output.Append('\n'); } using (var writer = new StreamWriter( Console.OpenStandardOutput(), new UTF8Encoding(false), 1 << 16)) { writer.Write(output); } } }