Skip to Content

Multiplayer Moo

Editorial oficial (C++) 

Solución en video

Por David Zhou

Nota: La solución en video podría no ser la misma que las otras soluciones. Código en C++, Python y Java.

Video de YouTube (6gGildschA0)

Solución

Explicación

Primero hallamos la región de una vaca más grande mediante flood fills desde cada celda no visitada.

La región de dos vacas más grande técnicamente necesita una optimización para pasar en tiempo. Mapeamos cada celda a su componente obtenida por flood fill. Luego construimos aristas entre todas las celdas adyacentes de distinto color. Por último, en vez de hacer un flood fill desde cada par de colores adyacentes distintos, podemos usar las aristas entre componentes y sumar los tamaños de regiones enteras de una vez. Fijamos la primera componente e iteramos sobre todas las adyacentes para este flood fill. Los tamaños de región se determinan a partir del flood fill de una vaca de antes.

La solución usa un enfoque iterativo de flood fill en vez de uno recursivo.

Implementación

Complejidad temporal: O(N2log(N))\mathcal{O}(N^2\log(N))

#include <fstream> #include <iostream> #include <map> #include <set> #include <vector> using std::cout; using std::endl; using std::pair; using std::vector; /** @return los 4 vecinos cardinales de una posición */ vector<pair<int, int>> neighbors(int r, int c) { return {{r + 1, c}, {r - 1, c}, {r, c + 1}, {r, c - 1}}; } int main() { std::ifstream read("multimoo.in"); int side_len; read >> side_len; vector<vector<int>> grid(side_len, vector<int>(side_len)); for (int r = 0; r < side_len; r++) { for (int c = 0; c < side_len; c++) { read >> grid[r][c]; } } // contiene los ids de región de cada celda: las que tienen el mismo id están // conectadas vector<vector<int>> regions(side_len, vector<int>(side_len)); // region_cells[r] contiene las posiciones de las celdas con id de región r vector<vector<pair<int, int>>> region_cells; int one_biggest = 0; vector<vector<bool>> visited(side_len, vector<bool>(side_len)); // flood fill de las regiones para ver qué celdas están conectadas for (int r = 0; r < side_len; r++) { for (int c = 0; c < side_len; c++) { if (visited[r][c]) { continue; } int curr_region = region_cells.size(); vector<pair<int, int>> contained; vector<pair<int, int>> frontier{{r, c}}; visited[r][c] = true; // flood fill para hallar todas las celdas conectadas a la actual while (!frontier.empty()) { pair<int, int> curr = frontier.back(); frontier.pop_back(); contained.push_back(curr); regions[curr.first][curr.second] = curr_region; for (const auto &[nr, nc] : neighbors(curr.first, curr.second)) { if (0 <= nr && 0 <= nc && nr < side_len && nc < side_len && !visited[nr][nc] && grid[nr][nc] == grid[r][c]) { visited[nr][nc] = true; frontier.push_back({nr, nc}); } } } one_biggest = std::max(one_biggest, (int)contained.size()); region_cells.push_back(contained); } } // obtenemos las regiones adyacentes a otras regiones vector<std::set<int>> adj_regions(region_cells.size()); for (const vector<pair<int, int>> &reg : region_cells) { for (const auto &[r, c] : reg) { for (const auto &[nr, nc] : neighbors(r, c)) { if (0 <= nr && 0 <= nc && nr < side_len && nc < side_len && regions[nr][nc] != regions[r][c]) { adj_regions[regions[r][c]].insert(regions[nr][nc]); } } } } /** @return el id de vaca de una región */ auto region_id = [&](int r) { return grid[region_cells[r][0].first][region_cells[r][0].second]; }; // registro de pares de áreas de regiones que ya se procesaron std::map<pair<int, int>, std::set<int>> seen; int two_biggest = one_biggest; for (int r1 = 0; r1 < region_cells.size(); r1++) { for (int r2 : adj_regions[r1]) { pair<int, int> valid{region_id(r1), region_id(r2)}; if (valid.first > valid.second) { std::swap(valid.first, valid.second); } // si este par y región ya se procesaron, no empezamos if (seen[valid].count(r1)) { continue; } // flood fill a través de regiones enteras esta vez, no solo celdas int two_size = 0; vector<int> frontier{r1}; // regiones que visitamos actualmente vector<bool> curr_vis(region_cells.size()); curr_vis[r1] = true; while (!frontier.empty()) { int curr = frontier.back(); frontier.pop_back(); two_size += region_cells[curr].size(); seen[valid].insert(curr); for (int nr : adj_regions[curr]) { int nid = region_id(nr); if (!curr_vis[nr] && (valid.first == nid || valid.second == nid)) { curr_vis[nr] = true; frontier.push_back(nr); } } } two_biggest = std::max(two_biggest, two_size); } } std::ofstream("multimoo.out") << one_biggest << '\n' << two_biggest << endl; }
import java.io.*; import java.util.*; public class Multimoo { private int n, cnt, id = 1; private Map<Integer, Integer> idToSize = new HashMap<>(); private Map<Integer, Integer> idToColor = new HashMap<>(); private Map<Integer, List<Integer>> componentAdj = new HashMap<>(); private int[][] grid, ids; private int[] dirr = {1, 0, -1, 0}; private int[] dirc = {0, 1, 0, -1}; private void floodFill(int r, int c, int color) { for (int i = 0; i < 4; i++) { int nextR = r + dirr[i]; int nextC = c + dirc[i]; if (nextR >= 0 && nextR < n && nextC >= 0 && nextC < n && ids[nextR][nextC] == 0 && grid[nextR][nextC] == color) { ids[nextR][nextC] = id; cnt++; floodFill(nextR, nextC, color); } } } private int dfsTwoColor(int compId, int color1, int color2, Map<Integer, Boolean> visited) { if (visited.getOrDefault(compId, false) || (idToColor.get(compId) != color1 && idToColor.get(compId) != color2)) { return 0; } visited.put(compId, true); int totalSize = idToSize.get(compId); List<Integer> neighbors = componentAdj.getOrDefault(compId, new ArrayList<>()); for (int neighbor : neighbors) { totalSize += dfsTwoColor(neighbor, color1, color2, visited); } return totalSize; } public void solve() throws IOException { BufferedReader br = new BufferedReader(new FileReader("multimoo.in")); PrintWriter pw = new PrintWriter(new FileWriter("multimoo.out")); n = Integer.parseInt(br.readLine()); grid = new int[n][n]; ids = new int[n][n]; for (int i = 0; i < n; i++) { String[] line = br.readLine().split(" "); for (int j = 0; j < n; j++) { grid[i][j] = Integer.parseInt(line[j]); } } int res1 = 0; // hallamos el tamaño de cada región for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (ids[i][j] == 0) { ids[i][j] = id; cnt = 1; // contamos la celda actual idToColor.put(id, grid[i][j]); floodFill(i, j, grid[i][j]); res1 = Math.max(res1, cnt); idToSize.put(id, cnt); id++; } } } // construimos el grafo de adyacencia de componentes for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { // comprobamos el vecino derecho if (j + 1 < n && ids[i][j] != ids[i][j + 1]) { int id1 = ids[i][j], id2 = ids[i][j + 1]; componentAdj.computeIfAbsent(id1, k -> new ArrayList<>()).add(id2); componentAdj.computeIfAbsent(id2, k -> new ArrayList<>()).add(id1); } // comprobamos el vecino de abajo if (i + 1 < n && ids[i][j] != ids[i + 1][j]) { int id1 = ids[i][j], id2 = ids[i + 1][j]; componentAdj.computeIfAbsent(id1, k -> new ArrayList<>()).add(id2); componentAdj.computeIfAbsent(id2, k -> new ArrayList<>()).add(id1); } } } // quitamos duplicados de las listas de adyacencia for (List<Integer> neighbors : componentAdj.values()) { Collections.sort(neighbors); neighbors = neighbors.stream().distinct().collect( java.util.stream.Collectors.toList()); } int res2 = res1; // computamos regiones de dos colores recorriendo el grafo de componentes Set<String> processedPairs = new HashSet<>(); for (Map.Entry<Integer, List<Integer>> entry : componentAdj.entrySet()) { int compId = entry.getKey(); List<Integer> neighbors = entry.getValue(); for (int neighborId : neighbors) { int color1 = idToColor.get(compId); int color2 = idToColor.get(neighborId); // orden consistente para evitar trabajo duplicado if (color1 > color2) { int temp = color1; color1 = color2; color2 = temp; } String colorPair = color1 + "," + color2; if (processedPairs.contains(colorPair)) { continue; } processedPairs.add(colorPair); // recorremos la región de dos colores empezando desde esta componente Map<Integer, Boolean> visited = new HashMap<>(); int regionSize = dfsTwoColor(compId, color1, color2, visited); res2 = Math.max(res2, regionSize); } } pw.println(res1); pw.println(res2); br.close(); pw.close(); } public static void main(String[] args) throws IOException { new Multimoo().solve(); } }
from collections import defaultdict, deque def flood_fill(r, c, color, n, grid, ids, id_val, cnt): dirr = [1, 0, -1, 0] dirc = [0, 1, 0, -1] queue = deque([(r, c)]) while queue: curr_r, curr_c = queue.popleft() for i in range(4): next_r = curr_r + dirr[i] next_c = curr_c + dirc[i] if ( 0 <= next_r < n and 0 <= next_c < n and ids[next_r][next_c] == 0 and grid[next_r][next_c] == color ): ids[next_r][next_c] = id_val cnt[0] += 1 queue.append((next_r, next_c)) def dfs_two_color(start_comp, color1, color2, id_to_color, id_to_size, component_adj): visited = set() stack = [start_comp] total_size = 0 while stack: comp_id = stack.pop() if comp_id in visited: continue comp_color = id_to_color[comp_id] if comp_color != color1 and comp_color != color2: continue visited.add(comp_id) total_size += id_to_size[comp_id] for neighbor in component_adj[comp_id]: if neighbor not in visited: neighbor_color = id_to_color[neighbor] if neighbor_color == color1 or neighbor_color == color2: stack.append(neighbor) return total_size with open("multimoo.in", "r") as f: n = int(f.readline().strip()) grid = [] ids = [[0] * n for _ in range(n)] for i in range(n): row = list(map(int, f.readline().strip().split())) grid.append(row) id_val = 1 id_to_size = {} id_to_color = {} component_adj = defaultdict(list) res1 = 0 # hallamos el tamaño de cada región for i in range(n): for j in range(n): if ids[i][j] == 0: ids[i][j] = id_val cnt = [1] # contamos la celda actual (usamos lista por referencia) id_to_color[id_val] = grid[i][j] flood_fill(i, j, grid[i][j], n, grid, ids, id_val, cnt) res1 = max(res1, cnt[0]) id_to_size[id_val] = cnt[0] id_val += 1 # construimos el grafo de adyacencia de componentes de forma eficiente edge_set = set() for i in range(n): for j in range(n): # comprobamos el vecino derecho if j + 1 < n and ids[i][j] != ids[i][j + 1]: id1, id2 = ids[i][j], ids[i][j + 1] if id1 > id2: id1, id2 = id2, id1 edge_set.add((id1, id2)) # comprobamos el vecino de abajo if i + 1 < n and ids[i][j] != ids[i + 1][j]: id1, id2 = ids[i][j], ids[i + 1][j] if id1 > id2: id1, id2 = id2, id1 edge_set.add((id1, id2)) # convertimos el conjunto de aristas a lista de adyacencia for id1, id2 in edge_set: component_adj[id1].append(id2) component_adj[id2].append(id1) res2 = res1 # computamos regiones de dos colores recorriendo el grafo de componentes processed_pairs = set() for comp_id, neighbors in component_adj.items(): for neighbor_id in neighbors: color1 = id_to_color[comp_id] color2 = id_to_color[neighbor_id] # orden consistente para evitar trabajo duplicado if color1 > color2: color1, color2 = color2, color1 color_pair = (color1, color2) if color_pair in processed_pairs: continue processed_pairs.add(color_pair) # recorremos la región de dos colores empezando desde esta componente region_size = dfs_two_color( comp_id, color1, color2, id_to_color, id_to_size, component_adj ) res2 = max(res2, region_size) # escribimos la salida with open("multimoo.out", "w") as f: f.write(f"{res1}\n") f.write(f"{res2}\n")