알고리즘/Baekjoon
[C++] 백준 23288번 주사위 굴리기 2
kimyunseok
2021. 10. 25. 19:02
삼성 SW 역량 테스트 기출 문제 수록 문제.
구현 / 시뮬레이션 문제이다.
이해하기 어려웠고, 시간을 잡아먹었던(자꾸 8번째 테스트 케이스가 이상하게 나와서 고쳤다.) 것들이 있었다.
조건들을 정리해가면서 문제 풀이를 정리해 보겠다.
문제풀이
문제의 조건들을 정리해보면, 다음과 같은 순서로 구현하면 된다.
- 높이, 너비, 주사위 이동횟수를 입력받는다.
- 격자의 각 칸의 숫자를 입력받는다.
여기까지가 기본 입력이다. 그 후에 이동횟수만큼 아래를 반복한다.
- 현재 주사위의 이동방향으로 주사위를 이동한다(굴린다).
만일 이동하려는 방향에 칸이 없다면 반대 방향으로 굴린다. - 이동한 칸(R, C)에서 점수를 획득한다.
점수는 동서남북 방향으로 (R, C)와 같은 칸의 개수를 찾으면 된다. - BFS로 찾으면 된다. - 마지막으로 주사위 아랫면의 숫자에 따라 다음과 같이 방향을 옮긴다.
- 주사위 아랫면의 수 > (R, C) : 주사위 이동방향을 시계 방향으로 90도 회전한다.
- 주사위 아랫면의 수 < (R, C) : 주사위 이동방향을 반시계 방향으로 90도 회전한다.
- 주사위 아랫면의 수 == (R, C) : 주사위 이동방향을 변경하지 않는다.
위 조건들을 구현해주면 된다.
조건은 여기서 끝인데, 주사위 전개도를 나는 처음에 안쪽으로 접어서 주사위를 만들려 했다.
그러나 이렇게 할 경우에 주사위의 겉면이 아닌 안쪽 면에 숫자가 들어가게 되므로 틀린 조립 방식이 된다.
따라서 주사위 전개도를 바깥쪽으로 접어야 이 문제의 조건에 맞게 풀 수 있다.
코드를 살펴보자.
사용한 STL과 변수
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int height, width, moveCnt;
int map[21][21];
bool visit[21][21];
int curDiceBottomNum = 6;
int curDirection = 1;
int curR = 1;
int curC = 1;
int reverseDiceNum[7] = { -1, 6, 5, 4, 3, 2, 1 };
int reverseDirection[4] = {2, 3, 0, 1};
int adj[4] = { 2, 3, 5, 4 }; // 순서 : 북(0), 동(1), 남(2), 서(3)
pair<int, int> directions[4] = {
{-1, 0},
{0, 1},
{1, 0},
{0, -1}
};
int ans;
- height, width, moveCnt : 입력받는 N, M, K
- map 배열 : 격자의 칸 정보
- visit 배열 : BFS를 할 때 각 좌표를 방문했는지 여부를 저장하는 배열
- curDiceBottomNum : 현재 주사위의 어떤 수가 바닥면을 향하고 있는지 저장하는 변수
- curDirection : 현재 방향. 0(북), 1(동), 2(남), 3(서)
- curR, curC : 현재 주사위의 좌표
- reverseDiceNum 배열 : 각 숫자(index)의 맞은편에 있는 수를 저장하는 배열
- reverseDirection 배열 : 각 방향의 반대 방향을 저장하는 배열
- adj 배열 : 현재 바닥면의 북, 동, 남, 서 방향에 존재하는 숫자를 저장해두는 배열.
- direction 배열 : 격자에서 북, 동, 남, 서 방향으로 가기위해 더해줘야하는 값을 저장하는 배열
- ans : 출력하는 값.
메인함수와 주사위 진행 메서드 go()
void go(int idx) {
if (idx > moveCnt) { return; }
move();
getScore();
setDirection();
go(idx + 1);
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin >> height >> width >> moveCnt;
for (int i = 1; i <= height; i++) {
for (int j = 1; j <= width; j++) {
cin >> map[i][j];
}
}
go(1);
cout << ans;
return 0;
}
메인함수에서는 N, M, K와 격자의 정보를 입력받는다.
그리고나서 go()메서드를 출력한다. 매개변수는 현재 몇번 째 주사위를 굴릴 차례인지를 나타낸다.
go() 메서드를 보면 위에서 말한 로직이 메서드 순서대로 구현되어 있다.
그 전에 만일 주사위를 굴리는 번째 수가 주사위를 굴릴 횟수 K보다 크다면 return한다.
아니라면 다음과 같은 순서로 진행된다.
- move() 메서드 : 주사위를 이동 방향대로 움직인다.
현재 방향에 따라 direction 배열에서 현재 좌표(R, C)값에 향하는 방향의 좌표를 구한다.void move() { int nxtR = curR + directions[curDirection].first; int nxtC = curC + directions[curDirection].second; if (nxtR < 1 || nxtR > height || nxtC < 1 || nxtC > width) { curDirection = reverseDirection[curDirection]; nxtR = curR + directions[curDirection].first; nxtC = curC + directions[curDirection].second; } curR = nxtR; curC = nxtC; int prevDiceBottom = curDiceBottomNum; curDiceBottomNum = adj[curDirection]; int reverse = reverseDirection[curDirection]; adj[reverse] = prevDiceBottom; adj[curDirection] = reverseDiceNum[prevDiceBottom]; }
만일 좌표가 올바르지 않다면, 반대 방향의 좌표로 현재 좌표를 이동시켜준다.
이동시켜준 후에는 주사위의 바닥면을 바꿔준다. adj[이동방향]이 바닥면이 된다.
이 때 주사위를 굴렸을 때는 이동한 방향과 이동한 방향의 역방향 면의
숫자만 바뀐다는 것을 알면 이 문제를 쉽게 풀 수 있다.
(ex. 남쪽 방향으로 굴렸다면 어차피 동, 북쪽 면의 숫자는 변하지 않는다.)
위 로직을 그대로 구현했다.
온 방향의 반대 방향은 이전 면이 될 것이고
온 방향을 한번 더 가면 이전 면의 마주보는 면이 될 것이다.
이런 식으로 인접한 네 면의 숫자를 저장하는 adj 배열의 정보를 갱신해준다. - getScore() 메서드 : 현재 칸에서 동서남북 방향 중 계속해서 나아갈 때, 같은 칸이 몇 개 있는지 세는 메서드
BFS를 통해서 현재 칸과 같은 숫자의 칸들만 탐사해서 몇 개의 칸이 있는지 센다.void getScore() { int cnt = 1; int num = map[curR][curC]; queue<pair<int, int>> q; vector<pair<int, int>> visitPointVec; q.push({ curR, curC }); visitPointVec.push_back({ curR, curC }); visit[curR][curC] = true; while (!q.empty()) { int r = q.front().first; int c = q.front().second; q.pop(); for (pair<int, int> dir : directions) { int nxtR = r + dir.first; int nxtC = c + dir.second; if(nxtR < 1 || nxtR > height || nxtC < 1 || nxtC > width || visit[nxtR][nxtC] || map[nxtR][nxtC] != num) { continue; } visit[nxtR][nxtC] = true; q.push({ nxtR, nxtC }); visitPointVec.push_back({ nxtR, nxtC }); cnt++; } } for (int i = 0; i < visitPointVec.size(); i++) { int r = visitPointVec[i].first; int c = visitPointVec[i].second; visit[r][c] = false; } ans = ans + (cnt * num); }
이 때, 좌표를 visitPointVec이라는 벡터에 넣어주어서 visit 배열을 다시 false로 처리할 때의 시간을 줄였다.
후에 ans에 '현재 칸의 수 * 같은 숫자의 칸의 개수'를 더해준다. - setDirection() 메서드 : 주사위 아랫면에 따라 이동방향을 설정해주는 메서드.
위에서 설명했듯이, 여기가 내가 실수했던 부분이다.void setDirection() { int mapNum = map[curR][curC]; if (curDiceBottomNum > mapNum) { curDirection = curDirection + 1; } else if (curDiceBottomNum < mapNum) { curDirection = curDirection - 1; } //오래걸린이유, 주사위의 방향 조건을 < 0일 땐 3으로 주고 > 3일 땐 0으로 줘야하는데, 두 조건 모두 0으로 줘버림. if (curDirection < 0 ) { curDirection = 3; } else if(curDirection > 3){ curDirection = 0; } }
주석에도 나와있듯이 주사위의 방향 숫자가 음수일 땐 3으로, 4 이상일 땐 0으로 바꿔줘야 하는데
두 조건 모두 0으로 만들었다.
테케 7번까지 잘 나왔는데 8번부터 엄청 큰 수가 나오길래 뭘 잘못했지 생각했는데 이 부분이였다.
아무튼 주사위 아랫면의 수와 현재 칸의 수를 비교한 후에 이동방향을 설정해준다. - go(idx + 1) : 다음 번째 굴릴 차례로 넘어간다.
이제 go()메서드가 끝이났으면 ans값을 출력하면 된다.
1. 주사위 전개도 잘못 조립
2. 방향 overflow 잘못 설정
코드 전문은 위에서 확인이 가능하다.