using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics.X86; using System.Text; using YukicoderContest305.Problems; namespace YukicoderContest305.Problems { public class ProblemC : ProblemBase { public ProblemC() : base(false) { } [MethodImpl(MethodImplOptions.AggressiveOptimization)] protected override void SolveEach(IOManager io) { var n = io.ReadInt(); var m = io.ReadInt(); var graph = new BasicGraph(28 * n); var edges = new Dictionary<(int u, int v), int>(2 * m); var edgeChars = new char[m]; var from = new int[28 * n]; from.Fill(-1); const int NULL = 0; const int OK = 27; for (int i = 0; i < m; i++) { var u = io.ReadInt(); var v = io.ReadInt(); var cRaw = io.ReadChar(); var c = cRaw - 'a' + 1; u--; v--; edgeChars[i] = cRaw; edges[(u, v)] = i; edges[(v, u)] = i; graph.AddEdge(ToIndex(NULL, u), ToIndex(c, v)); graph.AddEdge(ToIndex(NULL, v), ToIndex(c, u)); for (int ch = 1; ch <= 26; ch++) { if (ch == c) { graph.AddEdge(ToIndex(ch, u), ToIndex(ch, v)); graph.AddEdge(ToIndex(ch, v), ToIndex(ch, u)); } else { graph.AddEdge(ToIndex(ch, u), ToIndex(OK, v)); graph.AddEdge(ToIndex(ch, v), ToIndex(OK, u)); } } graph.AddEdge(ToIndex(OK, u), ToIndex(OK, v)); graph.AddEdge(ToIndex(OK, v), ToIndex(OK, u)); } var queue = new Queue(); queue.Enqueue(ToIndex(NULL, 0)); var distances = new int[28 * n]; distances.Fill(int.MaxValue); distances[0] = 0; while (queue.Count > 0) { var current = queue.Dequeue(); foreach (var next in graph[current]) { if (distances[next].ChangeMin(distances[current] + 1)) { from[next] = current; queue.Enqueue(next); } } } if (distances[ToIndex(OK, n - 1)] == int.MaxValue) { io.WriteLine(-1); return; } var revCurrent = ToIndex(OK, n - 1); var sb = new StringBuilder(distances[revCurrent]); var history = new Stack(); while (from[revCurrent] != -1) { var edgeIndex = edges[(revCurrent % n, from[revCurrent] % n)]; history.Push(edgeIndex); sb.Append(edgeChars[edgeIndex]); revCurrent = from[revCurrent]; } var adj = 1; while (!edges.ContainsKey((0, adj))) { adj++; } var adjIndex = edges[(0, adj)]; //while (IsPalindrome(sb)) //{ // for (int i = 0; i < 2; i++) // { // sb.Append(edgeChars[adjIndex]); // history.Push(adjIndex); // } //} if (history.Count > 2 * n) { throw new Exception(); } io.WriteLine(history.Count); while (history.Count > 0) { io.WriteLine(history.Pop() + 1); } int ToIndex(int c, int v) => c * n + v; } bool IsPalindrome(StringBuilder sb) { for (int i = 0; i < sb.Length; i++) { if (sb[i] != sb[sb.Length - i - 1]) { return false; } } return true; } public interface IEdge { int To { get; } } public interface IWeightedEdge : IEdge { long Weight { get; } } public interface IGraph where TEdge : IEdge { ReadOnlySpan this[int node] { get; } int NodeCount { get; } } public interface IWeightedGraph : IGraph where TEdge : IWeightedEdge { } public readonly struct BasicEdge : IEdge { public int To { get; } public BasicEdge(int to) { To = to; } public override string ToString() => To.ToString(); public static implicit operator BasicEdge(int edge) => new BasicEdge(edge); public static implicit operator int(BasicEdge edge) => edge.To; } [StructLayout(LayoutKind.Auto)] public readonly struct WeightedEdge : IWeightedEdge { public int To { get; } public long Weight { get; } public WeightedEdge(int to) : this(to, 1) { } public WeightedEdge(int to, long weight) { To = to; Weight = weight; } public override string ToString() => $"[{Weight}]-->{To}"; public void Deconstruct(out int to, out long weight) => (to, weight) = (To, Weight); } public class BasicGraph : IGraph { private readonly List> _edges; public ReadOnlySpan this[int node] => _edges[node].AsSpan(); public int NodeCount => _edges.Count; public BasicGraph(int nodeCount) { _edges = new List>(nodeCount); for (int i = 0; i < nodeCount; i++) { _edges.Add(new List()); } } public void AddEdge(int from, int to) => _edges[from].Add(to); public void AddNode() => _edges.Add(new List()); } } } namespace YukicoderContest305 { class Program { static void Main(string[] args) { IProblem question = new ProblemC(); using var io = new IOManager(Console.OpenStandardInput(), Console.OpenStandardOutput()); question.Solve(io); } } } #region Base Class namespace YukicoderContest305.Problems { public interface IProblem { string Solve(string input); void Solve(IOManager io); } public abstract class ProblemBase : IProblem { protected bool HasMultiTestCases { get; } protected ProblemBase(bool hasMultiTestCases) => HasMultiTestCases = hasMultiTestCases; public string Solve(string input) { var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(input)); var outputStream = new MemoryStream(); using var manager = new IOManager(inputStream, outputStream); Solve(manager); manager.Flush(); outputStream.Seek(0, SeekOrigin.Begin); var reader = new StreamReader(outputStream); return reader.ReadToEnd(); } public void Solve(IOManager io) { var tests = HasMultiTestCases ? io.ReadInt() : 1; for (var t = 0; t < tests; t++) { SolveEach(io); } } protected abstract void SolveEach(IOManager io); } } #endregion #region Utils namespace YukicoderContest305 { public class IOManager : IDisposable { private readonly BinaryReader _reader; private readonly StreamWriter _writer; private bool _disposedValue; private byte[] _buffer = new byte[1024]; private int _length; private int _cursor; private bool _eof; const char ValidFirstChar = '!'; const char ValidLastChar = '~'; public IOManager(Stream input, Stream output) { _reader = new BinaryReader(input); _writer = new StreamWriter(output) { AutoFlush = false }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private char ReadAscii() { if (_cursor == _length) { _cursor = 0; _length = _reader.Read(_buffer); if (_length == 0) { if (!_eof) { _eof = true; return char.MinValue; } else { ThrowEndOfStreamException(); } } } return (char)_buffer[_cursor++]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public char ReadChar() { char c; while (!IsValidChar(c = ReadAscii())) { } return c; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadString() { var builder = new StringBuilder(); char c; while (!IsValidChar(c = ReadAscii())) { } do { builder.Append(c); } while (IsValidChar(c = ReadAscii())); return builder.ToString(); } public int ReadInt() => (int)ReadLong(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public long ReadLong() { long result = 0; bool isPositive = true; char c; while (!IsNumericChar(c = ReadAscii())) { } if (c == '-') { isPositive = false; c = ReadAscii(); } do { result *= 10; result += c - '0'; } while (IsNumericChar(c = ReadAscii())); return isPositive ? result : -result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private Span ReadChunk(Span span) { var i = 0; char c; while (!IsValidChar(c = ReadAscii())) { } do { span[i++] = c; } while (IsValidChar(c = ReadAscii())); return span.Slice(0, i); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public double ReadDouble() => double.Parse(ReadChunk(stackalloc char[32])); [MethodImpl(MethodImplOptions.AggressiveInlining)] public decimal ReadDecimal() => decimal.Parse(ReadChunk(stackalloc char[32])); public int[] ReadIntArray(int n) { var a = new int[n]; for (int i = 0; i < a.Length; i++) { a[i] = ReadInt(); } return a; } public long[] ReadLongArray(int n) { var a = new long[n]; for (int i = 0; i < a.Length; i++) { a[i] = ReadLong(); } return a; } public double[] ReadDoubleArray(int n) { var a = new double[n]; for (int i = 0; i < a.Length; i++) { a[i] = ReadDouble(); } return a; } public decimal[] ReadDecimalArray(int n) { var a = new decimal[n]; for (int i = 0; i < a.Length; i++) { a[i] = ReadDecimal(); } return a; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(T value) => _writer.Write(value.ToString()); [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteLine(T value) => _writer.WriteLine(value.ToString()); public void WriteLine(IEnumerable values, char separator) { var e = values.GetEnumerator(); if (e.MoveNext()) { _writer.Write(e.Current.ToString()); while (e.MoveNext()) { _writer.Write(separator); _writer.Write(e.Current.ToString()); } } _writer.WriteLine(); } public void WriteLine(T[] values, char separator) => WriteLine((ReadOnlySpan)values, separator); public void WriteLine(Span values, char separator) => WriteLine((ReadOnlySpan)values, separator); public void WriteLine(ReadOnlySpan values, char separator) { for (int i = 0; i < values.Length - 1; i++) { _writer.Write(values[i]); _writer.Write(separator); } if (values.Length > 0) { _writer.Write(values[^1]); } _writer.WriteLine(); } public void Flush() => _writer.Flush(); [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsValidChar(char c) => ValidFirstChar <= c && c <= ValidLastChar; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsNumericChar(char c) => ('0' <= c && c <= '9') || c == '-'; private void ThrowEndOfStreamException() => throw new EndOfStreamException(); protected virtual void Dispose(bool disposing) { if (!_disposedValue) { if (disposing) { _reader.Dispose(); _writer.Flush(); _writer.Dispose(); } _disposedValue = true; } } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } } public static class UtilExtensions { public static bool ChangeMax(ref this T value, T other) where T : struct, IComparable { if (value.CompareTo(other) < 0) { value = other; return true; } return false; } public static bool ChangeMin(ref this T value, T other) where T : struct, IComparable { if (value.CompareTo(other) > 0) { value = other; return true; } return false; } public static void SwapIfLargerThan(ref this T a, ref T b) where T : struct, IComparable { if (a.CompareTo(b) > 0) { (a, b) = (b, a); } } public static void SwapIfSmallerThan(ref this T a, ref T b) where T : struct, IComparable { if (a.CompareTo(b) < 0) { (a, b) = (b, a); } } public static void Sort(this T[] array) where T : IComparable => Array.Sort(array); public static void Sort(this T[] array, Comparison comparison) => Array.Sort(array, comparison); } public static class CollectionExtensions { private class ArrayWrapper { #pragma warning disable CS0649 public T[] Array; #pragma warning restore CS0649 } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span AsSpan(this List list) { return Unsafe.As>(list).Array.AsSpan(0, list.Count); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span GetRowSpan(this T[,] array, int i) { var width = array.GetLength(1); return MemoryMarshal.CreateSpan(ref array[i, 0], width); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span GetRowSpan(this T[,,] array, int i, int j) { var width = array.GetLength(2); return MemoryMarshal.CreateSpan(ref array[i, j, 0], width); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span GetRowSpan(this T[,,,] array, int i, int j, int k) { var width = array.GetLength(3); return MemoryMarshal.CreateSpan(ref array[i, j, k, 0], width); } public static void Fill(this T[] array, T value) => array.AsSpan().Fill(value); public static void Fill(this T[,] array, T value) => MemoryMarshal.CreateSpan(ref array[0, 0], array.Length).Fill(value); public static void Fill(this T[,,] array, T value) => MemoryMarshal.CreateSpan(ref array[0, 0, 0], array.Length).Fill(value); public static void Fill(this T[,,,] array, T value) => MemoryMarshal.CreateSpan(ref array[0, 0, 0, 0], array.Length).Fill(value); } public static class SearchExtensions { struct LowerBoundComparer : IComparer where T : IComparable { public int Compare(T x, T y) => 0 <= x.CompareTo(y) ? 1 : -1; } struct UpperBoundComparer : IComparer where T : IComparable { public int Compare(T x, T y) => 0 < x.CompareTo(y) ? 1 : -1; } // https://trsing.hatenablog.com/entry/2019/08/27/211038 public static int GetGreaterEqualIndex(this ReadOnlySpan span, T inclusiveMin) where T : IComparable => ~span.BinarySearch(inclusiveMin, new UpperBoundComparer()); public static int GetGreaterThanIndex(this ReadOnlySpan span, T exclusiveMin) where T : IComparable => ~span.BinarySearch(exclusiveMin, new LowerBoundComparer()); public static int GetLessEqualIndex(this ReadOnlySpan span, T inclusiveMax) where T : IComparable => ~span.BinarySearch(inclusiveMax, new LowerBoundComparer()) - 1; public static int GetLessThanIndex(this ReadOnlySpan span, T exclusiveMax) where T : IComparable => ~span.BinarySearch(exclusiveMax, new UpperBoundComparer()) - 1; public static int GetGreaterEqualIndex(this Span span, T inclusiveMin) where T : IComparable => ((ReadOnlySpan)span).GetGreaterEqualIndex(inclusiveMin); public static int GetGreaterThanIndex(this Span span, T exclusiveMin) where T : IComparable => ((ReadOnlySpan)span).GetGreaterThanIndex(exclusiveMin); public static int GetLessEqualIndex(this Span span, T inclusiveMax) where T : IComparable => ((ReadOnlySpan)span).GetLessEqualIndex(inclusiveMax); public static int GetLessThanIndex(this Span span, T exclusiveMax) where T : IComparable => ((ReadOnlySpan)span).GetLessThanIndex(exclusiveMax); public static int BoundaryBinarySearch(Predicate predicate, int ok, int ng) { while (Math.Abs(ok - ng) > 1) { var mid = (ok + ng) / 2; if (predicate(mid)) { ok = mid; } else { ng = mid; } } return ok; } public static long BoundaryBinarySearch(Predicate predicate, long ok, long ng) { while (Math.Abs(ok - ng) > 1) { var mid = (ok + ng) / 2; if (predicate(mid)) { ok = mid; } else { ng = mid; } } return ok; } public static BigInteger BoundaryBinarySearch(Predicate predicate, BigInteger ok, BigInteger ng) { while (BigInteger.Abs(ok - ng) > 1) { var mid = (ok + ng) / 2; if (predicate(mid)) { ok = mid; } else { ng = mid; } } return ok; } public static double BoundaryBinarySearch(Predicate predicate, double ok, double ng, double eps = 1e-9, int loopLimit = 1000) { var count = 0; while (Math.Abs(ok - ng) > eps && count++ < loopLimit) { var mid = (ok + ng) * 0.5; if (predicate(mid)) { ok = mid; } else { ng = mid; } } return (ok + ng) * 0.5; } public static double Bisection(Func f, double a, double b, double eps = 1e-9) { if (f(a) * f(b) >= 0) { throw new ArgumentException("f(a)とf(b)は異符号である必要があります。"); } const int maxLoop = 100; double mid = (a + b) / 2; for (int i = 0; i < maxLoop; i++) { if (f(a) * f(mid) < 0) { b = mid; } else { a = mid; } mid = (a + b) / 2; if (Math.Abs(b - a) < eps) { break; } } return mid; } } } #endregion