분류: BFS, 시뮬레이션, 백트래킹, 완전탐색 /
문제
문제 설명
길고 길었던 겨울이 끝나고 BOJ 마을에도 봄이 찾아왔다. BOJ 마을에서는 꽃을 마을 소유의 정원에 피우려고 한다. 정원은 땅과 호수로 이루어져 있고 2차원 격자판 모양이다.
인건비 절감을 위해 BOJ 마을에서는 직접 사람이 씨앗을 심는 대신 초록색 배양액과 빨간색 배양액을 땅에 적절하게 뿌려서 꽃을 피울 것이다. 이 때 배양액을 뿌릴 수 있는 땅은 미리 정해져있다.
배양액은 매 초마다 이전에 배양액이 도달한 적이 없는 인접한 땅으로 퍼져간다.
아래는 초록색 배양액 2개를 뿌렸을 때의 예시이다. 하얀색 칸은 배양액을 뿌릴 수 없는 땅을, 황토색 칸은 배양액을 뿌릴 수 있는 땅을, 하늘색 칸은 호수를 의미한다.
초록색 배양액과 빨간색 배양액이 동일한 시간에 도달한 땅에서는 두 배양액이 합쳐져서 꽃이 피어난다. 꽃이 피어난 땅에서는 배양액이 사라지기 때문에 더 이상 인접한 땅으로 배양액을 퍼트리지 않는다.
아래는 초록색 배양액 2개와 빨간색 배양액 2개를 뿌렸을 때의 예시이다.
배양액은 봄이 지나면 사용할 수 없게 되므로 주어진 모든 배양액을 남김없이 사용해야 한다. 예를 들어 초록색 배양액 2개와 빨간색 배양액 2개가 주어졌는데 초록색 배양액 1개를 땅에 뿌리지 않고, 초록색 배양액 1개와 빨간색 배양액 2개만을 사용하는 것은 불가능하다.
또한 모든 배양액은 서로 다른 곳에 뿌려져야 한다.
정원과 두 배양액의 개수가 주어져있을 때 피울 수 있는 꽃의 최대 개수를 구해보자.
입력
첫째 줄에 정원의 행의 개수와 열의 개수를 나타내는 N(2 ≤ N ≤ 50)과 M(2 ≤ M ≤ 50), 그리고 초록색 배양액의 개수 G(1 ≤ G ≤ 5)와 빨간색 배양액의 개수 R(1 ≤ R ≤ 5)이 한 칸의 빈칸을 사이에 두고 주어진다.
그 다음 N개의 줄에는 각 줄마다 정원의 각 행을 나타내는 M개의 정수가 한 개의 빈 칸을 사이에 두고 주어진다. 각 칸에 들어가는 값은 0, 1, 2이다. 0은 호수, 1은 배양액을 뿌릴 수 없는 땅, 2는 배양액을 뿌릴 수 있는 땅을 의미한다.
배양액을 뿌릴 수 있는 땅의 수는 R+G개 이상이고 10개 이하이다.
출력
첫째 줄에 피울 수 있는 꽃의 최대 개수를 출력한다.
풀이
#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
enum State { EMPTY, GREEN, RED, FLOWER };
struct xy {int x, y;};
int N, M, G, R, Y;
int board[50][50];
int dist[50][50];
State state[50][50];
State mask[10];
xy yellow[10];
int dx[] = {1, 0, -1, 0};
int dy[] = {0, 1, 0, -1};
int countFlower() {
queue<xy> Q;
for(int i=0; i<Y; i++) {
if(mask[i] == EMPTY) continue;
state[yellow[i].x][yellow[i].y] = mask[i];
Q.push(yellow[i]);
}
int cnt = 0;
while(not Q.empty()) {
const auto [x, y] = Q.front(); Q.pop();
if(state[x][y] == FLOWER) continue;
for(int dir = 4; dir--;) {
int nx = x + dx[dir], ny = y + dy[dir];
if(nx<0 or nx>=N or ny<0 or ny>= M) continue;
if(board[nx][ny] == 0) continue;
if(state[nx][ny] == FLOWER) continue;
if(state[nx][ny] == EMPTY) {
state[nx][ny] = state[x][y];
dist[nx][ny] = dist[x][y] + 1;
Q.push({nx, ny});
}
else if(state[nx][ny] != state[x][y] and dist[nx][ny] == dist[x][y] + 1) {
state[nx][ny] = FLOWER;
cnt++;
}
}
}
return cnt;
}
int main() {
ios::sync_with_stdio(0); cin.tie(0);
cin >> N >> M >> G >> R;
for(int i=0; i<N; i++) {
for(int j=0; j<M; j++) {
cin >> board[i][j];
if(board[i][j] == 2)
yellow[Y++] = {i, j};
}
}
for(int i=0; i<Y; i++) {
if(i < Y-G-R) mask[i] = EMPTY;
else if(i < Y-G) mask[i] = GREEN;
else mask[i]= RED;
}
int ans = 0;
do {
for(int i=0; i<N; i++)
for(int j=0; j<M; j++)
dist[i][j] = state[i][j] = EMPTY;
ans = max(ans, countFlower());
} while(next_permutation(mask, mask+Y));
cout << ans;
}
문제는 크게 두 부분으로 나뉜다.
- 배양액을 뿌릴 수 있는 땅에서 빨간색, 초록색 배양액을 뿌릴 땅을 정하는 부분. (완전 탐색)
- `YCG X Y-GCR`의 경우의 수 완전 탐색
- 객 색에 대해 백트래킹 수행해 백트래킹을 두 번을 겹쳐 해결 가능
- `next_permutation()`으로 간단하게 해결 가능
- 배양액이 전파되어 피는 꽃의 수를 세는 부분 (BFS)
# 배양액 뿌릴 땅 정하기 (완전 탐색)
// mask[Y] = { 0, ..., 0, 1, ..., 1, 2, ..., 2 }
for(int i = 0; i < Y; i++) {
if(i < Y-G-R) mask[i] = EMPTY;
else if(i < Y-G) mask[i] = GREEN;
else mask[i]= RED;
}
do {
// Q에 Y개의 후보지에서 선택된 G+R개의 땅의 좌표 저장
queue<xy> Q;
for(int i = 0; i < Y; i++) {
if(mask[i] == EMPTY) continue;
state[yellow[i].x][yellow[i].y] = mask[i]; // RED or GREEN
Q.push(yellow[i]);
}
/*
yCg+r 중 하나의 경우
*/
} while(next_permutation(mask, mask+Y));
`next_permutation()`에서 주의할 점은 `mask[]`의 값을 정렬된 순서로 넣어야한다는 점
# 배양액 전파 (BFS)
enum State { EMPTY, GREEN, RED, FLOWER };
int dist[N][M];
State state[N][M];
queue<xy> Q;
for(int i=0; i<Y; i++) {
if(mask[i] == EMPTY) continue;
state[yellow[i].x][yellow[i].y] = mask[i];
Q.push(yellow[i]);
}
while(not Q.empty()) {
const auto [x, y] = Q.front(); Q.pop();
if(state[x][y] == FLOWER) continue;
for(int dir = 4; dir--;) {
int nx = x + dx[dir], ny = y + dy[dir];
if(nx<0 or nx>=N or ny<0 or ny>= M) continue;
if(board[nx][ny] == 0) continue;
if(state[nx][ny] == FLOWER) continue;
if(state[nx][ny] == EMPTY) {
state[nx][ny] = state[x][y];
dist[nx][ny] = dist[x][y] + 1;
Q.push({nx, ny});
}
else if(state[nx][ny] != state[x][y]
and dist[nx][ny] == dist[x][y] + 1) {
state[nx][ny] = FLOWER;
flower++;
}
}
}
`Q`에는 다음 턴에 전파 가능한 배양액의 좌표가 들어있다.
BFS로 전파하다가 색이 다른 배야액을 만나면 `dist[][]`값을 비교해 같은 시간에 도달 했다면 꽃을 피운다.
꽃을 피우는 조건은 색이 다른 BFS가 같은 시간에 도달함이기 때문에 모든 칸은 시간(`dist[][]`)과 색(state[][]`)을 상태로 갖는다.
이때 시간이 다르다면 더이상 전파하지 않고 무시한다.
다른 색이 이미 지나간 자리를 지나간 뒤 같은 시간에 다른 색을 만날 가능성이 없기 때문이다.
큐에서 뺐을 때 `if(state[x][y] == FLOWER)`을 또 검사하는 이유는
두 색이 겹쳐져 꽃이 핀 경우 먼저 해당 칸에 도달한 색은 큐에 들어가 있고
같은 칸에 도달한 두 번째 색은 꽃을 피우고 큐에 들어가지 않는다.
큐에 들어간 색은 큐에서 나올 때 해당 칸이 꽃이기 때문에 배양액 전파를 수행하면 안된다.
'Problem Solving > BOJ' 카테고리의 다른 글
[백준 - 5639] 이진 검색 트리 - C++ (0) | 2023.12.21 |
---|---|
[백준 - 1208] 부분수열의 합 2 - C++ (0) | 2023.12.20 |
[백준 - 1987] 알파벳 - C++ (0) | 2023.12.10 |
[백준 - 1916] 최소비용 구하기 - C++ (0) | 2023.12.10 |
[백준 - 17143] 낚시왕 - C++ (0) | 2023.12.09 |