import std.algorithm, std.array, std.conv, std.stdio, std.typecons; alias Edge = Tuple!(uint, uint); // must be sorted struct Triangle { // assert(A < B && B < C) uint A, B, C; this (uint a, uint b, uint c) { A = a, B = b, C = c; } // X is A, B or C // true: A->B->C, false: A->C->B uint perm(uint X, bool orientation) { if (orientation) { if (X == A) return B; else if (X == B) return C; else if (X == C) return A; else assert(0); } else { if (X == B) return A; else if (X == C) return B; else if (X == A) return C; else assert(0); } } } bool solve(bool[Triangle] triangle_set) { // the map from edge to triangles bool[Triangle][Edge] edge_to_triangles; foreach (triangle, _; triangle_set) foreach (edge; [Edge(triangle.A, triangle.B), Edge(triangle.B, triangle.C), Edge(triangle.A, triangle.C)] ) if (edge in edge_to_triangles) edge_to_triangles[edge][triangle] = true; else edge_to_triangles[edge] = [triangle: true]; // check if some more than triangles share a single edge if (edge_to_triangles.values.any!(ts => ts.length > 2)) return false; // unoriented triangles auto unoriented = triangle_set.dup; // orientations that are determined bool[Triangle] orientations; // return: true = compatiblly set bool recursively_set(Triangle tri, bool ori) { // already appears if (tri in orientations) { if (orientations[tri] == ori) return true; // compatible else return false; // not compatible } // first time appears else { // tri has orientation A -> B -> C -> A orientations[tri] = true; unoriented.remove(tri); // three edges auto edge1 = Edge(tri.A, tri.B), edge2 = Edge(tri.B, tri.C), edge3 = Edge(tri.A, tri.C); // the triangles that is next to 'triangle' auto ts1 = edge_to_triangles[edge1].dup, ts2 = edge_to_triangles[edge2].dup, ts3 = edge_to_triangles[edge3].dup; ts1.remove(tri), ts2.remove(tri), ts3.remove(tri); // results auto res1 = true, res2 = true, res3 = true; if (ts1.length != 0) { auto neighborhood1 = ts1.keys[0]; // neighborhood1 must have the orientation B -> A res1 = recursively_set(neighborhood1, neighborhood1.perm(tri.B, true) == tri.A); } if (ts2.length != 0) { auto neighborhood2 = ts2.keys[0]; // neighborhood2 must have the orientation C -> B res2 = recursively_set(neighborhood2, neighborhood2.perm(tri.C, true) == tri.B); } if (ts3.length != 0) { auto neighborhood3 = ts3.keys[0]; // neighborhood3 must have the orientation A -> C res3 = recursively_set(neighborhood3, neighborhood3.perm(tri.A, true) == tri.C); } return res1 && res2 && res3; } } while (unoriented.length > 0) { auto uot = unoriented.keys[0]; if (!recursively_set(uot, true)) return false; } return true; } void main() { // read auto M = readln[0 .. $-1].to!uint; // get the set of triangles bool[Triangle] triangle_set; foreach (m; 0 .. M) { auto ABC = readln.split.to!(uint[]); assert(ABC[0] < ABC[1] && ABC[1] < ABC[2]); triangle_set[Triangle(ABC[0], ABC[1], ABC[2])] = true; } writeln(triangle_set.solve ? "YES" : "NO"); }