백준의 성곽(2234) 문제이다.
( 문제 바로가기 )
[ 문제풀이 ]
1. 문제를 보면 일단 입력이 상당히 마음에 안든다. 이상한 숫자들이 막 적혀있다. 읽어보면 대충 이런말이다.
성에 방이 있는데(MAP에서 한칸을 의미) 그 방에는 벽이 존재한다. 따라서 벽이 존재하는 곳으로는 이동할 수 없고 벽이 없는
곳으로만 이동할 수 있는데 그 벽이 있는 위치를 입력으로 숫자로 나타낸 것이다.
서쪽 = 1 , 북쪽 = 2, 동쪽 = 4, 남쪽 = 8로 표시한다고 되어있는데, 이 말은...
예를 들어 값이 1이라면, 해당 지점에서는 서쪽에만 벽이 있음을 의미한다.
값이 3이라면, 3 = 1 + 2, 즉 서쪽과 북쪽에 있다는 것을 의미한다.
예제 입력의 첫줄을 보면 11 6 11 6 3 10 6 으로 이루어져있는데 이 말의 의미는 (0,0)의 값은 11 = 8 + 2 + 1 = 남,북,서 쪽에
벽이있다. (0, 1)의 값은 6 = 4 + 2 = 동쪽과 북쪽에 벽이 있다는 의미이다.
2. 이제 입력이 무슨 의미인지 알았으니 구하고자 하는 것을 구해보자.
1. 이 성에 있는 방의 갯수
2. 가장 넓은 방의 넓이
3. 하나의 벽을 제거하여 얻을 수 있는 가장 넓은 방의크기
이 3개를 구해야하는데... BFS를 사용한다면 3가지 모두 어렵지 않게 구할 수 있을 것이란 느낌이 든다.
물론, BFS안에서 움직일 수 있는 조건을 잘 생각해줘야 한다.
벽이 어디에 있는지 판단만 잘하고 움직일 때와 움직일 수 없을 때만 잘 구분지어 준다면, 3가지 값은 쉽게 구할 것이다.
3. 그렇다면 움직일 수 있는 조건에 대해서 알아보자. 알아볼 것도 없다. 벽이 없으면 된다. 그렇다면 벽이 있고 없고를
어떻게 판단해야할까? 답은 & 연산을 이용하면 쉽게 구할 수 있다.
그렇다면 먼저 일반적인 BFS에서를 한 점에서 상하좌우로 움직이는 코드에 대해서 보자.
물론 수많은 문제들에 따라서 조건이 다르고 갈 수 있는 방향이 다르기 때문에 다르겠지
만 가장 기본적이고 보편적인 방식을 적어본 것이다.(본인의 코드일 뿐입니다. 정답이 아닙니다.)
이 문제에서는 저 4방향으로 탐색할 때 벽이 있는지 없는지를 같이 탐색해줘야 한다.
탐색하는 방법은 어렵지 않다. 저 안에서 맵의 해당정점의 값과, [1, 2, 4, 8] 이 4개의 값에 대해서 &연산을 해보면 된다.
예를들어 현재 정점의 값이 11 이라고 생각해보자.
11 = 8 + 2 + 1 로 이루어져 있고, 이는 서, 북, 남쪽에 벽이 있다는 의미이다.
위에서 말한 차례대로 & 연산을 해보자.
11 & 1 = 1011 & 0001 = 0001
11 & 2 = 1011 & 0010 = 0010
11 & 4 = 1011 & 0100 = 0000
11 & 8 = 1011 & 1000 = 1000
보면 1,2,4,8을 순서대로 연산을 하면서 그 결과값이 &연산을 한값과 똑같이 나오면 벽이 있다는 의미이다.
구체적으로 설명해보겠다. 먼저 서쪽벽을 의미하는 1과 연산 한 부분을 보자.
1011 과 0001 을 연산하면 0001이 나오게된다. 즉 결과값이 서쪽벽을 의미하는 1이라는 값이 나오게 된다.
이와 같은 논리에 의해서 서쪽, 남쪽, 북쪽 벽은 모두 그 벽이 의미하는 값이 나오게 되지만
벽이 없는 동쪽벽은 보면 0000 이라는 동쪽벽을 의미하는 4라는 값이 나오지 않는다.
이러한 방식을 통해서 벽의 여부를 판단할 수 있다.
코드를 이렇게 구현한다면, 벽이 없는 곳에 대해서만 진행할 수 있을 것이다.
4. 이제 어려운 부분은 끝났다.
1. 이 성에 있는 방의 갯수
2. 가장 넓은 방의 넓이
3. 하나의 벽을 제거하여 얻을 수 있는 가장 넓은 방의크기
이 3가지를 구하는 방법을 설명해보겠다.
1번은 BFS를 호출하는 횟수가 될 것이다. 본인은 방문하지 않은 정점에 대해서만 BFS를 호출하였고, 이렇게 구현을 했다면
BFS의 호출 횟수가 방의 갯수가 될 것이다. ( 잘 생각해보면 방의 갯수 = BFS호출횟수 라는 것을 알 수 있음 )
2번은 BFS를 돌리면서 방의 갯수를 Count할 변수 하나만 두고, 방문할 수 있는 정점이라서 Q에 push될 때마다 그 변수값을
++시켜주는 연산을 해준다면 가장 넓은 방의 크기를 구할 수 있을 것이다.
3번은 한번 더 생각해봐야 하는 부분이다. 존재하는 모든 벽을 하나씩 다 없애봐야 한다.
벽을 없애는 방법도 어렵지 않다.
위에서 말한대로 현재 정점의 값과 [1, 2, 4, 8] 4개의 값을 연산하면 벽이 어느방향에 있는지 판단할 수 있다고는 위에서 말했다.
그렇다면 이제는 벽을 없애보자.
11을 예로들면, [1, 2, 8] 이 3가지 값에 대해서는 1,2,8의 값이 그대로 나온다. 즉, 이 정점에서 없애 볼수 있는 벽이 있다는 의미이
다. 없애는 것은 그 값을 빼주면된다.
무슨 소린지 말하는 나도 모르겠다. 11에서 1을 빼줘보자. 그럼 10이라는 값이 되고 이는 8 + 2 로써 북쪽과 남쪽에만 벽이 있음
을 의미하는 값이 된다. 즉, 해당 벽이 의미하는 값을 해당 정점이 가진 값에서 빼주면 그 벽을 없애는 것과 동일한 효과라는
것이다.
11에서 2를 빼면? 9가 되는데 이는 8 + 1이 되고, 이 값은 서쪽과 남쪽에만 벽이 있음을 의미하는 값이 된다.
이렇게 해당정점의 값과 벽의 의미하는 값들인 [1, 2, 4, 8] 을 & 연산을 통해서 벽이 있는 곳이라면, 그 값을 빼주고
BFS를 실행하게 되면, 그 벽을 제외했을 때의 BFS결과가 나오게 된다.
물론, 어느 한 지점에서 벽을 없애고 진행해서 원하는 결과를 알았다면, 그 값을 다시 더해줌으로써 벽을 다시 생성해주는
과정도 필요하다 !
[ 소스코드 ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | #include<iostream> #include<queue> #include<cstring> #define endl "\n" #define MAX 50 using namespace std; int N, M; int MAP[MAX][MAX]; bool Visit[MAX][MAX]; int dx[] = { 0, -1, 0, 1 }; int dy[] = { -1, 0, 1, 0 }; int Bigger(int A, int B) { if (A > B) return A; return B; } void Input() { cin >> M >> N; for (int i = 0; i < N; i++) { for (int j = 0; j < M; j++) { cin >> MAP[i][j]; } } } int BFS(int a, int b) { int Room_Size = 0; queue<pair<int, int>> Q; Q.push(make_pair(a, b)); Visit[a][b] = true; Room_Size++; while (Q.empty() == 0) { int x = Q.front().first; int y = Q.front().second; Q.pop(); int Wall = 1; for (int i = 0; i < 4; i++) { if ((MAP[x][y] & Wall) != Wall) { int nx = x + dx[i]; int ny = y + dy[i]; if (nx >= 0 && ny >= 0 && nx < N && ny < M) { if (Visit[nx][ny] == false) { Room_Size++; Visit[nx][ny] = true; Q.push(make_pair(nx, ny)); } } } Wall = Wall * 2; } } return Room_Size; } void Solution() { int Room_Cnt = 0; int Biggest_Room_Size = 0; for (int i = 0; i < N; i++) { for (int j = 0; j < M; j++) { if (Visit[i][j] == false) { Biggest_Room_Size = Bigger(Biggest_Room_Size, BFS(i,j)); Room_Cnt++; } } } cout << Room_Cnt << endl; cout << Biggest_Room_Size << endl; for (int i = 0; i < N; i++) { for (int j = 0; j < M; j++) { for (int Wall = 1; Wall <= 8; Wall *= 2) { if ((MAP[i][j] & Wall) == Wall) { memset(Visit, false, sizeof(Visit)); MAP[i][j] = MAP[i][j] - Wall; Biggest_Room_Size = Bigger(Biggest_Room_Size, BFS(i, j)); MAP[i][j] = MAP[i][j] + Wall; } } } } cout << Biggest_Room_Size << endl; } void Solve() { Input(); Solution(); } int main(void) { ios::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); //freopen("Input.txt", "r", stdin); Solve(); return 0; } | cs |
'[ BOJ Code ] > # BOJ -' 카테고리의 다른 글
[ 백준 16197 ] 두 동전 (C++) (0) | 2018.12.17 |
---|---|
[ 백준 15685 ] 드래곤 커브 (C++) (4) | 2018.12.14 |
[ 백준 9461 ] 파도반 수열 (C++) (0) | 2018.12.14 |
[ 백준 2422 ] 한윤정이 이탈리아에 가서 아이스크림을 사먹는데.. (C++) (0) | 2018.12.14 |
[ 백준 3190 ] 뱀 (C++) (0) | 2018.12.13 |