문제 설명


로봇개발자 무지는 한 달 앞으로 다가온 카카오배 로봇경진대회에 출품할 로봇을 준비하고 있습니다. 준비 중인 로봇은 2 x 1 크기의 로봇으로 무지는 0 1로 이루어진 N x N 크기의 지도에서 2 x 1 크기인 로봇을 움직여 (N, N) 위치까지 이동 할 수 있도록 프로그래밍을 하려고 합니다. 로봇이 이동하는 지도는 가장 왼쪽, 상단의 좌표를 (1, 1)로 하며 지도 내에 표시된 숫자 0은 빈칸을 1은 벽을 나타냅니다. 로봇은 벽이 있는 칸 또는 지도 밖으로는 이동할 수 없습니다. 로봇은 처음에 아래 그림과 같이 좌표 (1, 1) 위치에서 가로방향으로 놓여있는 상태로 시작하며, 앞뒤 구분없이 움직일 수 있습니다.

로봇이 움직일 때는 현재 놓여있는 상태를 유지하면서 이동합니다. 예를 들어, 위 그림에서 오른쪽으로 한 칸 이동한다면 (1, 2), (1, 3) 두 칸을 차지하게 되며, 아래로 이동한다면 (2, 1), (2, 2) 두 칸을 차지하게 됩니다. 로봇이 차지하는 두 칸 중 어느 한 칸이라도 (N, N) 위치에 도착하면 됩니다.

로봇은 다음과 같이 조건에 따라 회전이 가능합니다.

위 그림과 같이 로봇은 90도씩 회전할 수 있습니다. 단, 로봇이 차지하는 두 칸 중, 어느 칸이든 축이 될 수 있지만, 회전하는 방향(축이 되는 칸으로부터 대각선 방향에 있는 칸)에는 벽이 없어야 합니다. 로봇이 한 칸 이동하거나 90도 회전하는 데는 걸리는 시간은 정확히 1초 입니다.

0 1로 이루어진 지도인 board가 주어질 때, 로봇이 (N, N) 위치까지 이동하는데 필요한 최소 시간을 return 하도록 solution 함수를 완성해주세요.

제한사항

  • board의 한 변의 길이는 5 이상 100 이하입니다.
  • board의 원소는 0 또는 1입니다.
  • 로봇이 처음에 놓여 있는 칸 (1, 1), (1, 2)는 항상 0으로 주어집니다.
  • 로봇이 항상 목적지에 도착할 수 있는 경우만 입력으로 주어집니다.

입출력 예

boardresult

[[0, 0, 0, 1, 1],[0, 0, 0, 1, 0],[0, 1, 0, 1, 1],[1, 1, 0, 0, 1],[0, 0, 0, 0, 0]] 7

입출력 예에 대한 설명

문제에 주어진 예시와 같습니다.
로봇이 오른쪽으로 한 칸 이동 후, (1, 3) 칸을 축으로 반시계 방향으로 90도 회전합니다. 다시, 아래쪽으로 3칸 이동하면 로봇은 (4, 3), (5, 3) 두 칸을 차지하게 됩니다. 이제 (5, 3)을 축으로 시계 방향으로 90도 회전 후, 오른쪽으로 한 칸 이동하면 (N, N)에 도착합니다. 따라서 목적지에 도달하기까지 최소 7초가 걸립니다.

 

 

접근 방법


 출발 지점으로 부터 도착지까지 최소 이동횟수를 구하는 문제로 BFS를 사용하여 구할 수 있습니다. 이 때 한 턴에서 이동하는 방법은 상하좌우 4가지,  회전하는 경우 4가지로 총 8가지의 방법이 있습니다.

 

1. BFS를 사용하기 위해 방문여부를 체크해야 하는데 이 때 visited[x][y][x][y]의 4차원 배열로 확인을 하였고, 로봇은 2개의 좌표로 이루어져 있기 때문에 visited[x1][y1][x2][y2], visited[x2][y2][x1][y1]을 같은 경우의 수로 생각해야 합니다. 

 

2. 로봇이 상하좌우로 이동할 때에는 이동하려는 곳에 벽이 있는지 범위를 벗어나지 않았는지에 대해 확인을 하면 어렵지 않게 구현을 할 수 있습니다.

 

3. 로봇이 회전을 하는 경우에 대해서는 조금 생각을 해야 하는데, 2번에서의 조건을 만족하면서 회전 동작을 진행 할 때 방해하는 벽이 있는지 먼저 확인을 해야합니다.

 

저와 같은 경우는 아래 그림과 같이 구현을 하였습니다.

 

만약 아래의 그림과 같이 로봇을 회전 시키고 싶다면 로봇아래 두칸(검은 선이 그려진 부분)에 벽이 있는지 확인을 합니다.

확인을 한 이후에 벽이 없다면 회전을 시키면 됩니다. 자세한 내용은 아래 소스코드에 주석으로 달아 놓았습니다.

 //dir이 4~7일 때는 회전에 대한 동작이며 그에 해당하는 경우에 대한 논리
 //coord nextDir[4] = {{1,0}, {0,1}, {-1,0}, {0,-1}};
 //tmp는 robot의 상태로 초기화 했으며 회전한 상태를 return하기 위한 변수
 //state가 true면 로봇이 가로로 놓여있는 상태, false면 새로로 놓여있는 상태
 bool state = (tmp[0].x == tmp[1].x) ? true:false; 
 	int dirtmp = dir - 4; //dir의 값을 미리 저장

	if(state) dir = (dir < 6) ? 0 : 2;
	else dir = (dir < 6) ? 1 : 3;
	
    //로봇은 좌표 2칸을 차지하기 때문에 robot.size()인 2개의 좌표를 모두 검사해야함
	for(int i=0;i<robot.size();i++){
        tmp[i].x = robot[i].x + nextDir[dir].x;
        tmp[i].y = robot[i].y + nextDir[dir].y;
        //범위를 벗어나거나 로봇이 회전하는데 방해하는 벽이 있다면
        if(!inRange(tmp[i]) || map[tmp[i].x][tmp[i].y]) 
        	return robot;    
    }

//회전을 할 수 있다면 방향에 맞게 회전
if(dirtmp == 0 || dirtmp == 2) tmp[0] = robot[1];
else tmp[1] = robot[0];

 

 

 

소스 코드


#include <string>
#include <queue>
#include <vector>

using namespace std;

typedef struct coord{
    int x, y;
};

coord nextDir[4] = {{1,0}, {0,1}, {-1,0}, {0,-1}};
bool visited[101][101][101][101];
vector<vector<int>> map;
int N;

bool inRange(coord &xy){
    return (0 <= xy.x && xy.x < N && 0 <= xy.y && xy.y < N);
}

bool isVisited(vector<coord> &robot){
    return (visited[robot[0].x][robot[0].y][robot[1].x][robot[1].y] || visited[robot[1].x][robot[1].y][robot[0].x][robot[0].y]);
}

bool isDest(vector<coord> &robot){
    for(int i=0;i<robot.size();i++)
        if(robot[i].x == N - 1 && robot[i].y == N - 1)
            return true;
    return false;
}

vector<coord> nextState(vector<coord> robot, int dir){
    
    vector<coord> tmp = robot;
    
    if(dir < 4){
        for(int i=0;i<robot.size();i++){
            tmp[i].x = robot[i].x + nextDir[dir].x;
            tmp[i].y = robot[i].y + nextDir[dir].y;
            if(!inRange(tmp[i]) || map[tmp[i].x][tmp[i].y]) //범위를 벗어나거나 벽이라면
                return robot;
        }
    }
    else{
        //state가 true면 가로로 놓여있는 상태, false면 새로로 놓여있는 상태
        bool state = (tmp[0].x == tmp[1].x) ? true:false; 
        int dirtmp = dir - 4;
        
        if(state) dir = (dir < 6) ? 0 : 2;
        else dir = (dir < 6) ? 1 : 3;
        
        for(int i=0;i<robot.size();i++){
            tmp[i].x = robot[i].x + nextDir[dir].x;
            tmp[i].y = robot[i].y + nextDir[dir].y;
            if(!inRange(tmp[i]) || map[tmp[i].x][tmp[i].y]) //범위를 벗어나거나 벽이라면
                return robot;    
        }
        
        if(dirtmp == 0 || dirtmp == 2) tmp[0] = robot[1];
        else tmp[1] = robot[0];
    }
    return tmp;
}

int BFS(){
    
    vector<coord> robot = {{0,0},{0,1}};
    queue<pair<vector<coord>, int>> q; //로봇과 이동시간
    
    q.push({robot, 0});
    visited[0][0][0][1] = true;
    visited[0][1][0][0] = true;
    
    while(!q.empty()){
        
        vector<coord> now = q.front().first;
        int cnt = q.front().second;
        q.pop();
        
        for(int i=0;i<8;i++){
        
            vector<coord> next = nextState(now, i);
            
            if(!isVisited(next)){
                if(isDest(next))
                    return cnt + 1;
                q.push({next, cnt + 1});
                visited[next[0].x][next[0].y][next[1].x][next[1].y] = true;
                visited[next[1].x][next[1].y][next[0].x][next[0].y] = true;
            }
        }
    }
    return 0;
}

int solution(vector<vector<int>> board) {
    
    N = board.size();
    map = board;
    int answer = BFS();
    return answer;
}

문제 설명


문제 설명

게임 개발자인 베로니는 개발 연습을 위해 다음과 같은 간단한 카드 짝맞추기 보드 게임을 개발해 보려고 합니다.
게임이 시작되면 화면에는 카드 16장이 뒷면을 위로하여 4 x 4 크기의 격자 형태로 표시되어 있습니다. 각 카드의 앞면에는 카카오프렌즈 캐릭터 그림이 그려져 있으며, 8가지의 캐릭터 그림이 그려진 카드가 각기 2장씩 화면에 무작위로 배치되어 있습니다.
유저가 카드를 2장 선택하여 앞면으로 뒤집었을 때 같은 그림이 그려진 카드면 해당 카드는 게임 화면에서 사라지며, 같은 그림이 아니라면 원래 상태로 뒷면이 보이도록 뒤집힙니다. 이와 같은 방법으로 모든 카드를 화면에서 사라지게 하면 게임이 종료됩니다.

게임에서 카드를 선택하는 방법은 다음과 같습니다.

  • 카드는 커서를 이용해서 선택할 수 있습니다.
    • 커서는 4 x 4 화면에서 유저가 선택한 현재 위치를 표시하는 굵고 빨간 테두리 상자를 의미합니다.
  • 커서는 [Ctrl] 키와 방향키에 의해 이동되며 키 조작법은 다음과 같습니다.
    • 방향키 ←, ↑, ↓, → 중 하나를 누르면, 커서가 누른 키 방향으로 1칸 이동합니다.
    • [Ctrl] 키를 누른 상태에서 방향키 ←, ↑, ↓, → 중 하나를 누르면, 누른 키 방향에 있는 가장 가까운 카드로 한번에 이동합니다.
      • 만약, 해당 방향에 카드가 하나도 없다면 그 방향의 가장 마지막 칸으로 이동합니다.
    • 만약, 누른 키 방향으로 이동 가능한 카드 또는 빈 공간이 없어 이동할 수 없다면 커서는 움직이지 않습니다.
  • 커서가 위치한 카드를 뒤집기 위해서는 [Enter] 키를 입력합니다.
    • [Enter] 키를 입력해서 카드를 뒤집었을 때
      • 앞면이 보이는 카드가 1장 뿐이라면 그림을 맞출 수 없으므로 두번째 카드를 뒤집을 때 까지 앞면을 유지합니다.
      • 앞면이 보이는 카드가 2장이 된 경우, 두개의 카드에 그려진 그림이 같으면 해당 카드들이 화면에서 사라지며, 그림이 다르다면 두 카드 모두 뒷면이 보이도록 다시 뒤집힙니다.

베로니는 게임 진행 중 카드의 짝을 맞춰 몇 장 제거된 상태에서 카드 앞면의 그림을 알고 있다면, 남은 카드를 모두 제거하는데 필요한 키 조작 횟수의 최솟값을 구해 보려고 합니다. 키 조작 횟수는 방향키와 [Enter] 키를 누르는 동작을 각각 조작 횟수 1로 계산하며, [Ctrl] 키와 방향키를 함께 누르는 동작 또한 조작 횟수 1로 계산합니다.

다음은 카드가 몇 장 제거된 상태의 게임 화면에서 커서를 이동하는 예시입니다.
아래 그림에서 빈 칸은 이미 카드가 제거되어 없어진 칸을 의미하며, 그림이 그려진 칸은 카드 앞 면에 그려진 그림을 나타냅니다.


예시에서 커서는 두번째 행, 첫번째 열 위치에서 시작하였습니다.


[Enter] 입력, ↓ 이동, [Ctrl]+→ 이동, [Enter] 입력 = 키 조작 4회


[Ctrl]+↑ 이동, [Enter] 입력, [Ctrl]+← 이동, [Ctrl]+↓ 이동, [Enter] 입력 = 키 조작 5회


[Ctrl]+→ 이동, [Enter] 입력, [Ctrl]+↑ 이동, [Ctrl]+← 이동, [Enter] 입력 = 키 조작 5회

위와 같은 방법으로 커서를 이동하여 카드를 선택하고 그림을 맞추어 카드를 모두 제거하기 위해서는 총 14번(방향 이동 8번, [Enter] 키 입력 6번)의 키 조작 횟수가 필요합니다.


[문제]

현재 카드가 놓인 상태를 나타내는 2차원 배열 board와 커서의 처음 위치 r, c가 매개변수로 주어질 때, 모든 카드를 제거하기 위한 키 조작 횟수의 최솟값을 return 하도록 solution 함수를 완성해 주세요.

[제한사항]

  • board는 4 x 4 크기의 2차원 배열입니다.
  • board 배열의 각 원소는 0 이상 6 이하인 자연수입니다.
    • 0은 카드가 제거된 빈 칸을 나타냅니다.
    • 1 부터 6까지의 자연수는 2개씩 들어있으며 같은 숫자는 같은 그림의 카드를 의미합니다.
    • 뒤집을 카드가 없는 경우(board의 모든 원소가 0인 경우)는 입력으로 주어지지 않습니다.
  • r은 커서의 최초 세로(행) 위치를 의미합니다.
  • c는 커서의 최초 가로(열) 위치를 의미합니다.
  • r과 c는 0 이상 3 이하인 정수입니다.
  • 게임 화면의 좌측 상단이 (0, 0), 우측 하단이 (3, 3) 입니다.

[입출력 예]

boardrcresult

[[1,0,0,3],[2,0,0,0],[0,0,0,2],[3,0,1,0]] 1 0 14
[[3,0,0,2],[0,0,1,0],[0,1,0,0],[2,0,0,3]] 0 1 16

 

접근 방법


삼성 유형과 비슷한 문제였는데 상당히 복잡하게 느껴졌습니다.

 

일단 문제의 큰 틀을 잡아보면 4가지 과정으로 나눌 수 있습니다. 

1. 어떤 짝 먼저 맞춰갈지에 대한 순서 정하기

2. 순서를 정했다면 한 쌍에서 어떤 카드부터 선택 할지 정하기

3. 2번 과정까지 완료 됬다면 각각 지점사이에 이동 횟수를 알아내어 전체 경로의 이동횟수를 구하기

4. 3번 과정을 반복하며 최소값 갱신 

 

1. 어떤 짝 먼저 맞춰갈지에 대한 순서 정하기

 만약 짝이 1,2,3이 있다면 1->2->3 순서로 해결할지 1->3->2의 순서로 해결할지

3->1->2 등등의 순서를 정해주어야 합니다. 즉 모든 가능한 순열을 구해야 합니다.

void combinations(vector<bool> &visited, vector<vector<int>> &combs, vector<int> comb, vector<int> &card){
    
    if(comb.size() == card.size()){
        combs.push_back(comb);
        return;
    }
    for(int i=0;i<card.size();i++){
        if(!visited[i]){
            visited[i] = true;
            comb.push_back(card[i]);
            combinations(visited, combs, comb, card);
            comb.pop_back();
            visited[i] = false;
        }
    }
}

 combs = 순열들의 집합, comb = 한 순열, visited = 방문여부, card = 짝(ex, {1,2,3})

 순열은 백트래킹 기법을 사용하여 구할 수 있습니다.

 

이 때 card는 아래와 같은 방법으로 구할 수 있습니다. 또한 카드의 좌표정보를 갖고있는 info를 갱신하여 줍니다. info[카드번호] = {{x1,y1}, {x2, y2}}

//map == board
vector<int> findcard(){
    
    vector<bool> check_card(7, false);
    
    for(int i=0;i<4;i++)
        for(int j=0;j<4;j++)
            if(map[i][j]!=0){
                check_card[map[i][j]] = true;
                info[map[i][j]].push_back({i, j});
            }
    
    vector<int> card;
    for(int i=0;i<check_card.size();i++)
        if(check_card[i])
            card.push_back(i);
    
    return card;
}

 

 

2. 순서를 정했다면 한 쌍에서 어떤 카드부터 선택 할지 정하기

 각각의 짝은 2장의 카드로 이루어져 있습니다. 만약 [a->b->c->d]라는 순서를 결정지었을 때

a라는 짝이 a1, a2 두 장의 카드로 이루어져 있다면 a1을 먼저 방문하고 a2를 방문할지, a2를 먼저 방문하고 a1을 방문할지 결정하고 b, c, d에 대해서도 각각의 카드의 방문 순서를 지정해주어야 합니다.  

ex 1) a1 -> a2 -> b1 -> b2 -> c2 -> c1 -> d2 -> d1

ex 2) a2 -> a1 -> b1 -> b2 -> c1 -> c2 -> d2 -> d1

void decide_path(vector<vector<pair<int, int>>> &paths, vector<pair<int, int>> path, vector<int> &comb, int idx){
    
    if(idx == comb.size()){
        paths.push_back(path);
        return;
    }
    
    int num = comb[idx];
    path.push_back(info[num][0]);
    path.push_back(info[num][1]);
    decide_path(paths, path, comb, idx+1);
    path.pop_back();
    path.pop_back();
    path.push_back(info[num][1]);
    path.push_back(info[num][0]);
    decide_path(paths, path, comb, idx+1);
    path.pop_back();
    path.pop_back();
}

paths = path들의 집합, path = 이동할 좌표의 순열, comb = 한 순열

이동할 좌표의 순열 또한 백트래킹을 사용하여 구할 수 있습니다. 

 

3. 2번 과정까지 완료가 되었다면 각각 지점사이에 이동 횟수를 알아내어 전체 경로의 이동횟수를 구해야 합니다.

 

 한 좌표에서 다른 좌표로 이동할 수 있는 경우는 총 8가지가 있습니다. 상하좌우 한 칸씩 이동하는 방법 4개, ctrl키를 누른뒤 상하좌우로 이동하는 방법이 4개 있습니다. 이 때 저는 BFS를 통해 해결을 하였습니다.

int BFS(pair<int, int> start, pair<int, int> end){
    
    queue<pair<pair<int, int>, int>> q; //좌표와 레벨
    vector<vector<bool>> visited(4, vector<bool>(4,false));
    
    q.push({start, 0});
    visited[start.first][start.second] = true;
    
    while(!q.empty()){
     
        int x = q.front().first.first;
        int y = q.front().first.second;
        int level = q.front().second;
        q.pop();
        
        for(int i=0;i<8;i++){ //8가지 방향에 대해
            pair<int, int> next_xy = findNext(i,x,y); //다음 좌표!!!
            int next_x = next_xy.first;
            int next_y = next_xy.second;
            if(inRange(next_x, next_y) && !visited[next_x][next_y]){
                visited[next_x][next_y] = true;
                if(next_x == end.first && next_y == end.second)
                    return level + 1;
                q.push({{next_x, next_y}, level+1});    
            }
        }
    }
    return 0;
}

 위와 같이 BFS를 사용하여 start(출발 좌표)와 end(도착 좌표)까지의 최소 이동횟수를 구할 수 있는데 findNext()함수를  통해 다음 좌표를 구할 수 있습니다.

 

findNext(int dir, int x, int y)

pair<int, int> findNext(int dir, int x, int y){
    
    vector<pair<int, int>> nextdir = {{1,0},{0,1},{-1,0},{0,-1}};
    
    //처음 4방향은 1칸만 이동
    if(dir < 4)
        return { x + nextdir[dir].first, y + nextdir[dir].second};
    else{ //나머지 4방향은 끝 또는 카드가 있는 곳까지 이동(ctrl + 방향키)
        dir -= 4;
        while(inRange(x + nextdir[dir].first, y + nextdir[dir].second)){
         
            x += nextdir[dir].first;
            y += nextdir[dir].second;    
            if(tmp[x][y] != 0)
                break;
        }
        return {x, y};
    }
}

 

 

4. 3번 과정을 반복하며 최소값 갱신 

int processOneComb(vector<int> &comb, int r, int c){
    
    vector<vector<pair<int, int>>> paths;
    vector<pair<int, int>> path;
    
    decide_path(paths, path, comb, 0);
    int mindist = 0xFFFFFFF;
    
    for(int i=0;i<paths.size();i++){
        
        tmp = map; //한 순열에 대한 board상태 초기화
        int dist = 0;
        
        for(int j=0;j<paths[i].size();j++){ //한 경로 조합에 대한 거리
            //각각 dist에 +1하는 이유는 엔터동작 때문임
            if(j==0) dist += BFS({r,c}, paths[i][j]) + 1;
            else dist += BFS(paths[i][j - 1], paths[i][j]) + 1;
            tmp[paths[i][j].first][paths[i][j].second] = 0; //이동한 좌표의 카드 삭제
        }    
        mindist = min(mindist, dist);
    }
    return mindist;
}

 

 

소스 코드


#include <string>
#include <vector>
#include <algorithm>
#include <queue>

using namespace std;

vector<pair<int,int>> info[7];
vector<vector<int>> map;
vector<vector<int>> tmp;

bool inRange(int x, int y){
    return (0 <= x && x < 4 && 0 <= y && y < 4);
}

void combinations(vector<bool> &visited, vector<vector<int>> &combs, vector<int> comb, vector<int> &card){
    
    if(comb.size() == card.size()){
        combs.push_back(comb);
        return;
    }
    for(int i=0;i<card.size();i++){
        if(!visited[i]){
            visited[i] = true;
            comb.push_back(card[i]);
            combinations(visited, combs, comb, card);
            comb.pop_back();
            visited[i] = false;
        }
    }
}

vector<vector<int>> find_combs(vector<int> &card){
    
    vector<bool> visited(card.size(), false);
    vector<vector<int>> combs;
    vector<int> comb;
    combinations(visited, combs, comb, card);
    return combs;
}

vector<int> findcard(){
    
    vector<bool> check_card(7, false);
    
    for(int i=0;i<4;i++)
        for(int j=0;j<4;j++)
            if(map[i][j]!=0){
                check_card[map[i][j]] = true;
                info[map[i][j]].push_back({i, j});
            }
    
    vector<int> card;
    for(int i=0;i<check_card.size();i++)
        if(check_card[i])
            card.push_back(i);
    
    return card;
}

void decide_path(vector<vector<pair<int, int>>> &paths, vector<pair<int, int>> path, vector<int> &comb, int idx){
    
    if(idx == comb.size()){
        paths.push_back(path);
        return;
    }
    
    int num = comb[idx];
    path.push_back(info[num][0]);
    path.push_back(info[num][1]);
    decide_path(paths, path, comb, idx+1);
    path.pop_back();
    path.pop_back();
    path.push_back(info[num][1]);
    path.push_back(info[num][0]);
    decide_path(paths, path, comb, idx+1);
    path.pop_back();
    path.pop_back();
}

pair<int, int> findNext(int dir, int x, int y){
    
    vector<pair<int, int>> nextdir = {{1,0},{0,1},{-1,0},{0,-1}};
    
    if(dir < 4)
        return { x + nextdir[dir].first, y + nextdir[dir].second};
    else{
        dir -= 4;
        while(inRange(x + nextdir[dir].first, y + nextdir[dir].second)){
         
            x += nextdir[dir].first;
            y += nextdir[dir].second;    
            if(tmp[x][y] != 0)
                break;
        }
        return {x, y};
    }
}

int BFS(pair<int, int> start, pair<int, int> end){
    
    queue<pair<pair<int, int>, int>> q; //좌표와 레벨
    vector<vector<bool>> visited(4, vector<bool>(4,false));
    
    q.push({start, 0});
    visited[start.first][start.second] = true;
    
    while(!q.empty()){
     
        int x = q.front().first.first;
        int y = q.front().first.second;
        int level = q.front().second;
        q.pop();
        
        for(int i=0;i<8;i++){
            pair<int, int> next_xy = findNext(i,x,y);
            int next_x = next_xy.first;
            int next_y = next_xy.second;
            if(inRange(next_x, next_y) && !visited[next_x][next_y]){
                visited[next_x][next_y] = true;
                if(next_x == end.first && next_y == end.second)
                    return level + 1;
                q.push({{next_x, next_y}, level+1});    
            }
        }
    }
    return 0;
}

int processOneComb(vector<int> &comb, int r, int c){
    
    vector<vector<pair<int, int>>> paths;
    vector<pair<int, int>> path;
    
    decide_path(paths, path, comb, 0);
    int mindist = 0xFFFFFFF;
    
    for(int i=0;i<paths.size();i++){
        
        tmp = map; //한 순열에 대한 board상태 초기화
        int dist = 0;
        
        for(int j=0;j<paths[i].size();j++){ //한 경로 조합에 대한 거리
            //각각 dist에 +1하는 이유는 엔터동작 때문임
            if(j==0) dist += BFS({r,c}, paths[i][j]) + 1;
            else dist += BFS(paths[i][j - 1], paths[i][j]) + 1;
            tmp[paths[i][j].first][paths[i][j].second] = 0; //이동한 좌표의 카드 삭제
        }    
        mindist = min(mindist, dist);
    }
    return mindist;
}

int solution(vector<vector<int>> board, int r, int c) {
    
    int answer = 0xFFFFFFF;
    map = board;
    vector<int> card = findcard();
    vector<vector<int>> combs = find_combs(card);
    
    for(int i=0;i<combs.size();i++){
        answer = min(answer, processOneComb(combs[i], r, c));
    }
    
    return answer;
}

문제 링크


www.acmicpc.net/problem/19238

 

19238번: 스타트 택시

첫 줄에 N, M, 그리고 초기 연료의 양이 주어진다. (2 ≤ N ≤ 20, 1 ≤ M ≤ N2, 1 ≤ 초기 연료 ≤ 500,000) 연료는 무한히 많이 담을 수 있기 때문에, 초기 연료의 양을 넘어서 충전될 수도 있다. 다

www.acmicpc.net

 

접근 방법


문제를 해결하는 과정은 아래와 같습니다.

 

1. 현재 택시로 부터 가장 가까운 손님을 탐색하기 위해선 BFS를 사용해야 합니다. 이 때 거리가 같은 손님이 여러명 존재할 수 있기 때문에 가장 가까운 손님을 탐색완료하더라도 현재 레벨 까지는 계속해서 탐색을 해야합니다. 또한 탐색을 시작하기전 현재위치에 손님이 있는지도 먼저 검사를 해야합니다.

 

2. 손님의 위치가 탐색이 완료된다면 현재의 연료를 가지고 탐색한 위치로 이동 할 수 있는지 검사 후 이동을 합니다.

 

3. 1번 항목과 마찬가지로 BFS로 탐색을 하여 손님의 도착지 까지의 거리를 알아냅니다. 

 

4. 이후 현재의 연료를 가지고 이동 할 수 있는지 검사 후 이동을 시킵니다.  

 

5. 손님이 있던 좌표의 정보를 제거합니다.

 

6. 1번 항목부터 반복

 

자세한 사항은 소스코드에 주석을 달았습니다.

소스 코드


#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <string.h>
#define MAX 10000

using namespace std;

typedef struct coord {
	int x, y;
};

typedef struct tInfo {
	coord xy;
	int cost;
};

coord direction[4] = { {1,0}, {-1,0}, {0,1}, {0,-1} };
coord passengers[21][21];
int tMap[21][21];
int N, M;

bool inRange(int x, int y) {
	return (0 <= x && x < N && 0 <= y && y < N);
}

coord comp(coord one, coord other) {
	if (one.x < other.x)
		return one;
	else if (one.x == other.x) {
		if (one.y < other.y)
			return one;
		else
			return other;
	}
	else
		return other;
}

tInfo BFS(int x, int y, int state) { 
//state == 1 -> 택시가 손님에게
//state == 2 -> 손님을 태우고 목적지로

	queue <tInfo> q;
	vector<vector<bool>> visited(N, vector<bool>(N));
	coord ret = {MAX, MAX};
	int dist = 0;

	q.push({ x,y,0 });
	visited[x][y] = true;

	if (state == 1 && passengers[x][y].x != -1 && passengers[x][y].x != -1) //현재 위치에 손님이 있다면
		return { {x,y},0 };

	while (!q.empty()) {

		bool flag = false;
		int size = q.size();

		while (--size >= 0) { // 레벨 탐색

			int now_x = q.front().xy.x;
			int now_y = q.front().xy.y;
			int cost = q.front().cost;
			q.pop();

			for (int i = 0; i < 4; i++) {

				int next_x = now_x + direction[i].x;
				int next_y = now_y + direction[i].y;

				if (inRange(next_x, next_y)) {
					if (!visited[next_x][next_y] && tMap[next_x][next_y] == 0) {

						if (state == 1) { // 손님이 있는 곳으로
							if (passengers[next_x][next_y].x != -1 && passengers[next_x][next_y].x != -1) { //손님이 있는 곳
								flag = true; //탐색을 1번이라도 성공했다는 flag
								ret = comp(ret, { next_x, next_y }); //문제의 조건에 따라 손님의 우선순위 결정
								dist = cost + 1;
							}
						}
						else { //택시가 손님을 태우고 목적지로
							if (passengers[x][y].x == next_x && passengers[x][y].y == next_y) { //목적지에 도착했을 때
								ret = { next_x, next_y };
								return { ret, cost + 1};
							}
						}
						q.push({ next_x, next_y, cost + 1 });
						visited[next_x][next_y] = true;
					}
				}
			}
		}
		if (flag)
			break;
	}

	return { ret, dist };
}

bool canGo(tInfo &taxi, tInfo to, int state) {  
//state == 1 -> 택시가 손님한테 가는 동작
//state == 2 -> 손님이 목적지에 가는 동작 
	if (to.xy.x == MAX && to.xy.x == MAX)
		return false;
	if (taxi.cost - to.cost >= 0) {
		taxi.xy.x = to.xy.x;
		taxi.xy.y = to.xy.y;

		if (state == 1) // 손님을 태우러 감
			taxi.cost -= to.cost;
		else // 손님을 목적지로 이동 시킨 경우
			taxi.cost += to.cost;
			
		return true;
	}
	return false;
}

int solve(tInfo taxi) {

	while (--M >= 0) {

		tInfo toPassenger = BFS(taxi.xy.x, taxi.xy.y, 1); //1번 손님의 위치 탐색
		if (!canGo(taxi, toPassenger, 1)) //2번 손님으로 이동 가능하다면 택시 정보 갱신
			return -1;

		tInfo toDestination = BFS(taxi.xy.x, taxi.xy.y, 2); //3번 목적지의 거리와 좌표 탐색
		if (!canGo(taxi, toDestination, 2))  // 목적지로 이동 가능하다면 택시 정보 갱신
			return -1;

		passengers[toPassenger.xy.x][toPassenger.xy.y] = { -1,-1 }; //하차 후 손님 좌표의 정보 삭제
	}

	return taxi.cost;
}

int main() {

	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);

	int x, y, cost;
	
	tInfo taxi;
	
	cin >> N >> M >> cost;

	for (int i = 0; i < N; i++)
		for (int j = 0; j < N; j++) {
			cin >> tMap[i][j];
			passengers[i][j] = { -1,-1 }; //도착지의 정보가 0,0이 될 수 있기 때문에 -1로 초기화
		}
	
	cin >> x >> y;
	taxi = { {x - 1, y - 1}, cost };
	
	for (int i = 0; i < M; i++) {
		int x, y, next_x, next_y;
		cin >> x >> y >> next_x >> next_y;
		passengers[x - 1][y - 1] = { next_x - 1 , next_y - 1}; //출발지와 도착지 매핑
	}

	cout << solve(taxi);
}

 

문제 링크


https://www.acmicpc.net/problem/14502

 

14502번: 연구소

인체에 치명적인 바이러스를 연구하던 연구소에서 바이러스가 유출되었다. 다행히 바이러스는 아직 퍼지지 않았고, 바이러스의 확산을 막기 위해서 연구소에 벽을 세우려고 한다. 연구소는 크�

www.acmicpc.net

접근 방법


 위 문제는 벽 3개를 세울 수 있는 모든 경우에 대해 벽을 세운 후 바이러스를 퍼뜨려 안전영역의 최댓값을 구하면 된다. 

 

 벽을 3개를 세우는 방법은 백트래킹으로 가능하나 for문을 3중으로 사용하는 것도 가능하다. 필자는 3중 for문을 사용했으며, 취향에 따라 방법을 달리하여도 된다. 벽을 3개를 세운 뒤 바이러스를 퍼뜨리는 방법은 BFS를 사용하면 된다.

 

1. 일단 main()에서 입력을 받는 동시에 빈 공간의 위치와 바이러스의 위치의 정보를 저장해 두었다.

for (int i = 0; i < n; i++) 
	for (int j = 0; j < m; j++) {
    cin >> room[i][j];
    	if (room[i][j] == 0)
    		zeros.push_back({ i,j }); //빈공간
    	else if (room[i][j] == 2)
    		virus.push_back({ i,j }); //바이러스 초기위치
    }

2. 3중 for문을 사용하여 벽 3개를 세울 위치를 각각 first, second, third로 받았으며 해당 위치를 map에서 1로 변경 후 bfs를 사용하여 바이러스를 퍼뜨려 바이러스가 퍼진 공간을 cnt로 반환받았으며 안전영역의 수는 아래와 같이 구할 수 있다.

int safe = zeros.size() - 3 - cnt;

safe(바이러스가 퍼진 후 안전영역) =

zeros.size()(바이러스가 퍼지기 이전의 안전영역 수) - 3(벽을 새로 세운 공간) - cnt(바이러스가 퍼진 영역)

 

 

 

소스 코드


#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
#include <string.h>

using namespace std;

int room[8][8];
vector <pair<int, int>> zeros;
vector <pair<int, int>> virus;
pair<int, int> direction[4] = { {1,0}, {-1,0}, {0,1}, {0,-1} };
int n, m;
int max_ = 0;

int bfs(int x, int y) {

	int cnt = 0;
	queue < pair<int, int>> q;
	q.push({ x,y });
	while (!q.empty()) {
		auto elem = q.front();
		int i = elem.first;
		int j = elem.second;
		q.pop();
		for (int k = 0; k < 4; k++) {
			int next_x = i + direction[k].first;
			int next_y = j + direction[k].second;
			if (0 <= next_x && next_x < n && 0 <= next_y && next_y < m && room[next_x][next_y] == 0) {
				q.push({ next_x, next_y });
				room[next_x][next_y] = 2;
				cnt++;
			}
		}
	}
	return cnt;
}

void build_wall(int n, int m) {

	for (int i = 0; i < zeros.size(); i++) {
		for (int j = i + 1; j < zeros.size(); j++) {
			for (int k = j + 1; k < zeros.size(); k++) {
				int cnt = 0;
				auto first = zeros[i];
				auto second = zeros[j];
				auto third = zeros[k];
				room[first.first][first.second] = 1;
				room[second.first][second.second] = 1;
				room[third.first][third.second] = 1;
				for (auto &elem : virus) {
					cnt += bfs(elem.first, elem.second); //바이러스가 퍼진 영역의 수
				}
				int safe = zeros.size() - 3 - cnt;
				max_ = max(max_, safe);
				for (auto &elem : zeros) {
					room[elem.first][elem.second] = 0;
				}
				room[first.first][first.second] = 0;
				room[second.first][second.second] = 0;
				room[third.first][third.second] = 0;
			}
		}
	}
}

int main() {

	ios_base::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	memset(room, -1, sizeof(room));

	cin >> n >> m;
	for (int i = 0; i < n; i++) 
		for (int j = 0; j < m; j++) {
			cin >> room[i][j];
			if (room[i][j] == 0)
				zeros.push_back({ i,j }); //빈공간
			else if (room[i][j] == 2)
				virus.push_back({ i,j }); //바이러스 초기위치
		}
	build_wall(n, m);
	cout << max_ << "\n";
}

개발 환경 : VS 2019

질문, 지적 환영입니다.

'알고리즘 > 백준' 카테고리의 다른 글

[C++] 백준 2580 스도쿠  (0) 2020.06.10
[C++] 백준 4195 친구네트워크  (0) 2020.06.09
[C++] 백준 17779 게리맨더링2  (0) 2020.05.29
[C++] 백준 17837 새로운 게임 2  (0) 2020.05.29
[C++] 백준 1520 내리막길  (0) 2020.05.26

문제 링크


https://www.acmicpc.net/problem/2206

 

2206번: 벽 부수고 이동하기

N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 당신은 (1, 1)에서 (N, M)의 위치까지 이동하려 하는데, 이때 최단 경로��

www.acmicpc.net

 

접근 방법


 문제를 읽어보면 BFS알고리즘으로 풀어야된다는 직감은 확실히 들것이다. 하지만 벽을 부수는 것이 가능한 기회가 단 한번 주어지는대, 이로 인해 풀이에 있어 접근이 쉽지 않을 것이다.

 일단 문제를 보자마자 떠올릴 수 있는 방법이 있는데 map에서 모든 벽들을 한번씩 0으로 바꿔 보고 BFS를 해보는 것이다. 하지만 이렇게 하는 것은 굉장히 비효율적이고 시간안에 해결 할 수 없다. 

 문제 풀이에 있어 핵심은 어떤 특정한 지점에 "벽을 한번 부수고 난 이후에 도착 한 시간" 과 "벽을 한번도 부수지 않은 상태에서 도착한 시간"이 다르다는 것이다. 이것을 이해했다면 문제를 풀어보자.

 

(1) 변수

   isbreak는 벽을 부순적이 없다면 false 부순적이 있다면 true이다.

그렇다면 visited[i][j][ isbreak=true ]i, j위치에 벽을 부수고 나서 도착했을때의 시간이고,

visited[i][j][ isbreak=false ]벽을 부수지 않고 도착했을때의 시간이 된다. 

 

(2) 풀이

  전반적인 부분이 다른 BFS문제들과 매우 비슷하므로, 가장 핵심이 되는 부분만 설명하겠다.

if (!isbreak && map[next_x][next_y] == 1) {
	//벽을 부순적이 없는데 가로막힌 길이라면
    visited[next_x][next_y][true] = visited[x][y][false] + 1;
    q.push({ next_x, next_y, true });
}
else if (map[next_x][next_y] == 0 && visited[next_x][next_y][isbreak] == 0) { 
    //지나갈 수 있는 길이고, 방문한적이 없다면
    visited[next_x][next_y][isbreak] = visited[x][y][isbreak] + 1;
    q.push({ next_x, next_y, isbreak });
}

  첫 번째 if문은 isbreak = false이고 map[next_x][next_y] = 1 인 조건에서 진입이 가능한데, 이를 쉽게 풀어쓰면

"next_x, next_y의 좌표에 도달할 때 까지 벽을 부순적이 없는데 벽이 있다면" 진입을 한다는 것이다.

진입을 했다면 이제 벽을 부수고 해당지점에 도착했다는 것을 나타내주면 된다.

  두 번째 if문은 "일단 지나갈 수 있는 길이고, 방문한 적이 없다면" 방문을 하여 시간을 기록하고 큐에 push하면 된다. 이는 너무나도 당연한 것이기에 자세한 설명은 하지 않겠다.

 

  bfs( )함수의 while문 안에서 좌표가 n,m이면 도달한 시간을 return하게 된다. 그 말은 즉 while()안에서 return을 하지 못한다면 map[n][m]에 도달하지 못했다는 의미가 되므로 -1을 return한다. 

소스 코드


#include <iostream>
#include <queue>

using namespace std;

typedef struct info {
	int x;
	int y;
	bool isbreak;
};

int map[1001][1001];
int visited[1001][1001][2]; //같은 지점이여도 벽을 부수고 도착했을때와 벽을 부수지 않고 도착했을 때 값이 달라지기 때문
int n, m;

int bfs() {

	queue <info> q;
	visited[1][1][false] = 1;
	q.push({ 1,1,false }); //시작 지점 , 벽을 아직 부수지 않았음

	while (!q.empty()) {

		int x = q.front().x;
		int y = q.front().y;
		bool isbreak = q.front().isbreak;
		q.pop();

		if (x == n && y == m) //목표지점에 도착
			return visited[x][y][isbreak];

		pair<int, int> direction[4] = { {1,0}, {-1,0} ,{0,1} ,{0,-1} };

		for (int i = 0; i < 4; i++) {

			int next_x = x + direction[i].first;
			int next_y = y + direction[i].second;

			if (!(next_x < 1 || n < next_x || next_y < 1 || m < next_y)) { //다음 좌표가 map에 포함되어 있다면
				if (!isbreak && map[next_x][next_y] == 1) {
					//벽을 부순적이 없는데 가로막힌 길이라면
					visited[next_x][next_y][true] = visited[x][y][false] + 1;
					q.push({ next_x, next_y, true });
				}
				else if (map[next_x][next_y] == 0 && visited[next_x][next_y][isbreak] == 0) { 
					//지나갈 수 있는 길이고, 방문한적이 없다면
					visited[next_x][next_y][isbreak] = visited[x][y][isbreak] + 1;
					q.push({ next_x, next_y, isbreak });
				}
			}
		}
	}
	return -1;
}

int main() {

	cin >> n >> m;

	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			scanf("%1d", &map[i][j]);

	cout << bfs();
}

개발 환경 : VS 2019

질문, 지적 환영입니다.

'알고리즘 > 백준' 카테고리의 다른 글

[C++] 백준 15683 감시  (0) 2020.05.20
[C++] 백준 14500 테트로미노  (0) 2020.05.20
[C++] 백준 1697 숨바꼭질  (0) 2020.05.19
[C++] 백준 14501 퇴사  (0) 2020.05.19
[C++] 백준 12865 평범한 배낭  (2) 2020.05.18

문제 링크


https://www.acmicpc.net/problem/1697

 

1697번: 숨바꼭질

문제 수빈이는 동생과 숨바꼭질을 하고 있다. 수빈이는 현재 점 N(0 ≤ N ≤ 100,000)에 있고, 동생은 점 K(0 ≤ K ≤ 100,000)에 있다. 수빈이는 걷거나 순간이동을 할 수 있다. 만약, 수빈이의 위치가

www.acmicpc.net

 

접근 방법


어떤 지점에 도달하는데 있어 최초로 도달한 시간을 기록하는 문제이기에 BFS를 사용해야겠다고 떠올렸다.

 

(1) 변수

 visited[i]는 i에 도달하였을때의 시간을 기록해두며, 방문 여부도 동시에 판단할 수 있게 한다.

next[i]는 다음으로 이동할 지점이다.     

 

(2) 풀이

  다음 이동할 지점을 기록한 next[i] 가 도착지점(to)에 최초 도달할 때(visited[next[i]]가 -1인 상태) visited[now] + 1을

반환하면 된다.                                                                               

소스 코드


 

#include <iostream>
#include <queue>
#include <string.h>

using namespace std;

int visited[100001];

int bfs(int from, int to) {
	queue <int> q;
	q.push(from);
	visited[from] = 0;

	while (!q.empty()) {
		int now = q.front();
		int next[3] = { now + 1, now - 1, now * 2 };
		q.pop();
		for (int i = 0; i < 3; i++) 			
			if (0 <= next[i] && next[i] <= 100000 && visited[next[i]] == -1) {
				if (next[i] == to) return visited[now] + 1;
				q.push(next[i]);
				visited[next[i]] = visited[now] + 1;
			}
	}
	return 0;
}

int main() {
	int from, to;
    memset(visited, -1, sizeof(visited));
	cin >> from >> to;
	cout << bfs(from, to);
}

개발 환경 : VS 2019

질문, 지적 환영입니다.

'알고리즘 > 백준' 카테고리의 다른 글

[C++] 백준 14500 테트로미노  (0) 2020.05.20
[C++] 백준 2206 벽 부수고 이동하기  (0) 2020.05.19
[C++] 백준 14501 퇴사  (0) 2020.05.19
[C++] 백준 12865 평범한 배낭  (2) 2020.05.18
[C++] 백준 15686 치킨 배달  (0) 2020.05.18

+ Recent posts