import std.algorithm, std.array, std.conv, std.stdio, std.typecons; alias Edge = Tuple!(uint, uint); alias EdgeSet = bool[Edge]; alias Face = Tuple!(uint, uint, uint); alias FaceSet = bool[Face]; alias ASC = Tuple!(EdgeSet, "edges", FaceSet, "faces"); ASC create_normalized_asc(uint[][] face_list, ref uint N) { // normalize bool[uint] verts; foreach (face; face_list) { verts[face[0]] = true; verts[face[1]] = true; verts[face[2]] = true; } uint[uint] nv; uint N2; foreach (i; 1 .. N+1) { if (i in verts) nv[i] = N2++; } N = N2; EdgeSet edges; FaceSet faces; foreach (face; face_list) { edges[Edge(nv[face[0]], nv[face[1]])] = true; edges[Edge(nv[face[0]], nv[face[2]])] = true; edges[Edge(nv[face[1]], nv[face[2]])] = true; faces[Face(nv[face[0]], nv[face[1]], nv[face[2]])] = true; } return ASC(edges, faces); } // check if the ASC asc is connected bool is_connected(ASC asc, uint N) { bool[uint] vertex_set; auto uf = new UnionFind(N); foreach (face; asc.faces.keys) { uf.unite(face[0], face[1]); uf.unite(face[0], face[2]); vertex_set[face[0]] = vertex_set[face[1]] = vertex_set[face[2]] = true; } foreach (edge; asc.edges.keys) { uf.unite(edge[0], edge[1]); vertex_set[edge[0]] = vertex_set[edge[1]] = true; } return uf.size(vertex_set.keys[0]) == vertex_set.length; } // calculate link {i} for each i ASC[] links(ASC asc, uint N) { auto result = new ASC[N]; foreach (face; asc.faces.keys) { result[face[0]].edges[Edge(face[1], face[2])] = true; result[face[1]].edges[Edge(face[0], face[2])] = true; result[face[2]].edges[Edge(face[0], face[1])] = true; } return result; } uint H1_homology_rank(ASC asc, uint N) { // dim im d_2 F_2Vector[] d_2_matrix; auto edge_list = asc.edges.keys; foreach (face; asc.faces.keys) { F_2Vector vec; vec.set_entry(cast(uint) edge_list.countUntil(Edge(face[0], face[1])), 1); vec.set_entry(cast(uint) edge_list.countUntil(Edge(face[0], face[2])), 1); vec.set_entry(cast(uint) edge_list.countUntil(Edge(face[1], face[2])), 1); d_2_matrix ~= vec; } auto b_rank = get_rank(d_2_matrix); F_2Vector[] d_1_matrix; foreach (edge; asc.edges.keys) { F_2Vector vec; vec.set_entry(edge[0], 1); vec.set_entry(edge[1], 1); d_1_matrix ~= vec; } auto z_rank = cast(uint)asc.edges.length - get_rank(d_1_matrix); assert(z_rank >= b_rank); return z_rank - b_rank; } void main() { auto MN = readln.split.to!(uint[]); auto N = MN[0], M = MN[1]; uint[][] face_list; foreach (m; 0 .. M) { face_list ~= readln.split.to!(uint[]); } auto asc = create_normalized_asc(face_list, N); auto co = is_connected(asc, N); auto dim = H1_homology_rank(asc, N); auto lk = links(asc, N).map!(a => is_connected(a, N)).all!(a => a); if (!co) writeln("CO"); else if (dim != 0) writeln("H1"); else if (!lk) writeln("LK"); else writeln("CM"); } /*************************************************************************** ****************************************************************************/ alias F_2Vector = ulong[39]; // 39 * 8 > 300 static ulong_size = 8 * (ulong.sizeof); void set_entry(ref F_2Vector vector, uint n_th, int val) { if (val) vector[n_th / ulong_size] |= 1UL << n_th % ulong_size; else vector[n_th / ulong_size] &= ~0UL - (1UL << (n_th % ulong_size)); } bool is_zero(F_2Vector vector) { foreach (e; vector) if (e != 0) return false; return true; } F_2Vector slide_vector(F_2Vector vector) { F_2Vector slided; foreach (i; 0 .. vector.length - 1) { slided[i] = vector[i+1]; } return slided; } // by sweep // vectors can change uint get_rank(F_2Vector[] vectors ...) { if (vectors.all!(v => is_zero(v))) return 0; // make the left-most vector nonzero while (vectors.all!(v => v[0] == 0)) vectors.each!((ref v) => v = slide_vector(v)); while (vectors.all!(v => v[0] % 2 == 0)) vectors.each!((ref v) => v[0] >>= 1); foreach (ref v; vectors) { if (v[0] % 2 != 0) { auto tmp = vectors[0]; vectors[0] = v; v = tmp; } } // sweeping foreach (ref v; vectors[1 .. $]) { if (v[0] % 2 != 0) v[] ^= vectors[0][]; } vectors[1 .. $].map!((ref v) => v[0] >>= 1); return 1 + get_rank(vectors[1 .. $]); } // Union-tree of {0, 1, ..., num-1} class UnionFind { size_t num; private size_t[] parent; private size_t[] rank; private size_t[] size_; this (size_t num) { this.num = num; parent.length = num; foreach (n; 0 .. num) parent[n] = n; rank.length = num; size_.length = num; } size_t root(size_t n) { assert(n < num); if (parent[n] == n) return n; else return parent[n] = root(parent[n]); // recursively set the root } bool is_same(size_t n, size_t m) { assert(n < num && m < num); return root(n) == root(m); } // false if already united bool unite(size_t n, size_t m) { assert(n < num && m < num); auto rn = root(n), rm = root(m); if (rn == rm) return false; // swap if necessary to force rank[rm] <= rank[rn] if (rank[rn] < rank[rm]) { auto tmp = rm; rm = rn; rn = tmp; } parent[rm] = rn; if (rank[rn] == rank[rm]) ++rank[rn]; size_[rn] += size_[rm] + 1; return true; } size_t size(size_t n) { return size_[root(n)]+1; } size_t size() { size_t result; foreach (i; 0 .. num) if (root(i) == i) result += 1; return result; } }