Редакция для Игра «Жизнь»
Submitting an official solution before solving the problem yourself is a bannable offence.
Автор:
1. Идея
Нужно смоделировать игру «Жизнь» на прямоугольном поле в течение k шагов.
На каждом шаге состояние каждой клетки зависит только от:
- её текущего состояния;
- количества живых соседей среди
8возможных.
Самое естественное решение — просто выполнять симуляцию шаг за шагом:
- для каждой клетки посчитать число живых соседей;
- по правилам определить, какой она станет на следующем шаге;
- записать результат в новое поле;
- заменить текущее поле новым.
Важно, что все клетки обновляются одновременно. Значит, нельзя менять поле "на месте", иначе уже обновлённые клетки начнут влиять на ещё не обработанные. Поэтому нужен отдельный массив next_grid.
2. Наблюдения
Наблюдение 1. У клетки всегда не более 8 соседей
Соседи — это клетки по горизонтали, вертикали и диагонали. Удобно заранее сохранить их смещения:
(-1, -1)(-1, 0)(-1, 1)(0, -1)(0, 1)(1, -1)(1, 0)(1, 1)
Тогда для клетки (r, c) можно перебрать все 8 направлений и проверить, лежит ли сосед внутри поля.
Наблюдение 2. Клетки вне поля считаются мёртвыми
Это означает, что при подсчёте соседей достаточно учитывать только те координаты, которые находятся в пределах:
0 <= nr < n0 <= nc < m
Если координаты выходят за границы, такую клетку просто игнорируем.
Наблюдение 3. Правила перехода очень локальные
Для каждой клетки достаточно знать только число живых соседей alive.
Дальше правила такие:
- если клетка живая (
'X'), то она остаётся живой только приalive == 2илиalive == 3; - если клетка мёртвая (
'.'), то она оживает только приalive == 3.
Наблюдение 4. Ограничения позволяют прямую симуляцию
Максимальные значения:
n <= 200m <= 200k <= 200
На одном шаге:
n * mклеток;- для каждой смотрим до
8соседей.
То есть всего порядка k * n * m * 8, что укладывается без проблем.
3. Алгоритм
Пусть текущее поле хранится в grid.
Для каждого из k шагов:
- Создаём новое поле
next_grid, целиком заполненное символами'.'. - Для каждой клетки
(r, c):- считаем количество живых соседей
alive; - перебираем все 8 направлений;
- если сосед внутри поля и в
gridтам'X', увеличиваемalive.
- считаем количество живых соседей
- Применяем правила:
- если
grid[r][c] == 'X':- при
alive == 2илиalive == 3вnext_grid[r][c]записываем'X'; - иначе оставляем
'.';
- при
- если
grid[r][c] == '.':- при
alive == 3записываем'X'; - иначе оставляем
'.'.
- при
- если
- После обработки всех клеток присваиваем
grid = next_grid.
После завершения всех шагов выводим поле.
4. Почему это работает
Докажем, что алгоритм действительно получает правильное состояние поля после k шагов.
Рассмотрим один произвольный шаг симуляции.
Для каждой клетки алгоритм:
- точно перебирает все 8 возможных соседних позиций;
- учитывает только те из них, которые находятся внутри поля;
- среди них подсчитывает ровно те клетки, которые живы в текущем состоянии
grid.
Значит, число alive совпадает с количеством живых соседей по условию задачи.
Далее алгоритм применяет к клетке точно те же правила, что заданы в условии:
- живая клетка выживает при
2или3соседях; - мёртвая оживает при
3соседях; - во всех остальных случаях клетка мертва.
Новое состояние записывается в отдельное поле next_grid, поэтому вычисления для одних клеток не влияют на вычисления для других клеток на этом же шаге. Это соответствует требованию одновременного обновления.
Таким образом, после одного шага next_grid совпадает с правильным состоянием поля после одного применения правил.
Повторяя этот процесс k раз, получаем правильное состояние после ровно k шагов.
5. Сложность
На каждом из k шагов:
- обрабатывается
n * mклеток; - для каждой проверяются
8соседей.
Итоговая сложность:
O(k * n * m * 8), что обычно записывают какO(k * n * m).
По памяти:
- храним текущее и следующее поле размера
n * m.
Итоговая память:
O(n * m).
6. Код на C++17
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main() {
int n, m, k;
cin >> n >> m >> k;
vector<string> grid(n);
for (int i = 0; i < n; i++) {
cin >> grid[i];
}
int dr[8] = {-1, -1, -1, 0, 0, 1, 1, 1};
int dc[8] = {-1, 0, 1, -1, 1, -1, 0, 1};
for (int step = 0; step < k; step++) {
vector<string> next_grid(n, string(m, '.'));
for (int r = 0; r < n; r++) {
for (int c = 0; c < m; c++) {
int alive = 0;
for (int d = 0; d < 8; d++) {
int nr = r + dr[d];
int nc = c + dc[d];
if (nr >= 0 && nr < n && nc >= 0 && nc < m && grid[nr][nc] == 'X') {
alive++;
}
}
if (grid[r][c] == 'X') {
if (alive == 2 || alive == 3) {
next_grid[r][c] = 'X';
} else {
next_grid[r][c] = '.';
}
} else {
if (alive == 3) {
next_grid[r][c] = 'X';
} else {
next_grid[r][c] = '.';
}
}
}
}
grid = next_grid;
}
for (int i = 0; i < n; i++) {
cout << grid[i] << '\n';
}
return 0;
}
7. Код на Python 3
n, m, k = map(int, input().split())
grid = [list(input()) for _ in range(n)]
dr = [-1, -1, -1, 0, 0, 1, 1, 1]
dc = [-1, 0, 1, -1, 1, -1, 0, 1]
for _ in range(k):
next_grid = [['.' for _ in range(m)] for _ in range(n)]
for r in range(n):
for c in range(m):
alive = 0
for d in range(8):
nr = r + dr[d]
nc = c + dc[d]
if 0 <= nr < n and 0 <= nc < m and grid[nr][nc] == 'X':
alive += 1
if grid[r][c] == 'X':
if alive == 2 or alive == 3:
next_grid[r][c] = 'X'
else:
if alive == 3:
next_grid[r][c] = 'X'
grid = next_grid
for row in grid:
print(''.join(row))
Комментарии