Multiplayer Moo
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:
#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>> ® : 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")