문제 설명


문제 설명

게임 개발자인 베로니는 개발 연습을 위해 다음과 같은 간단한 카드 짝맞추기 보드 게임을 개발해 보려고 합니다.
게임이 시작되면 화면에는 카드 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;
}

접근 방법


 시뮬레이션 문제 입니다. 다만 문제를 조금 쉽게 풀기 위해서 생각해야 될 부분은

기둥과 보를 각각 2차원 배열로 따로 관리한다는 점과 삭제 연산 시에 현재 구조물을 삭제했을 때 영향을 받을 수 있는 주변의 구조물들이 온전하게 있을지에 대한 것입니다. 

 

시뮬레이션 과정은 총 4가지를 고려해야 합니다.

1. 기둥 설치, 2. 보 설치, 3. 기둥 삭제, 4. 보 삭제

 

기둥 배열을 bool col[][], 보 배열을 bool bo[][]라 한다면 

 

1의 경우

기둥이 바닥에 있거나 (y == 0)

보의 한쪽 끝 부분위에 있거나 (bo[y][x] == true || bo[y][x - 1] == true)

다른 기둥위에 있어야합니다. (col[y - 1][x] == true)

bool canBuildCol(int x, int y){
    return (y == 0 || col[y - 1][x] || bo[y][x] || bo[y][x - 1]);
}

 

2의 경우 

보는 한쪽 끝 부분이 기둥위에 있거나 (col[y - 1][x] || col[y - 1][x + 1])

양쪽 끝이 다른 보와 연결 되어 있어야 합니다. (bo[y][x - 1] && bo[y][x + 1])

bool canBuildBo(int x, int y){
    return (col[y - 1][x] || col[y - 1][x + 1] || (bo[y][x - 1] && bo[y][x + 1]));
}

 

3의 경우

기둥을 삭제하면 영향을 받을 수 있는 구조물은 3개가 있습니다.

빨간색 기둥을 삭제 한다면 2개의 파란색 보와, 윗 부분의 파란색 기둥이 영향을 받을 수 있는데 

 이러한 구조물이 없다면 삭제해도 되지만 구조물이 있다면 빨간색 구조물이 없을 때에도 온전하게 있을 수 있는지 검사를 해야합니다.

 

  (1) 기둥의 상단에 기둥이 있다면, 기둥을 삭제한 후 상단의 기둥이 온전히 있을 수 있는지

  (2) 기둥의 좌측 상단에 보가 있다면, 기둥을 삭제한 후 좌측 상단의 보가 온전히 있을 수 있는지

  (3) 기둥의 상단에 보가 있다면, 기둥을 삭제한 후 그 상단의 보가 온전히 있을 수 있는지 

  col[y][x] = false;
  if((col[y + 1][x] && !canBuildCol(x, y+1)) || 
  	(bo[y + 1][x] && !canBuildBo(x, y + 1)) || 
  	(bo[y + 1][x - 1] && !canBuildBo(x - 1, y + 1))){
  	
    col[y][x] = true;
  }

온전히 있을 수 없다면 다시 기둥을 설치합니다.

 

 

4의 경우 

보를 삭제하면 영향을 받을 수 있는 구조물은 4개가 있습니다. 

 빨간색 보를 삭제하게 된다면 양쪽 끝의 보 2개와 기둥 2개가 영향을 받습니다. 

위와 마찬가지로 4가지 구조물에 대한 검사를 합니다. 

 

  (1) 우측에 보가 있다면, 보를 삭제한 후 우측의 보가 온전히 있을 수 있는지 

  (2) 좌측에 보가 있다면, 보를 삭제한 후 좌측의 보가 온전히 있을 수 있는지 

  (3) 자신의 위치에 기둥이 있다면, 보를 삭제한 후 그 기둥이 온전히 있을 수 있는지 

  (4) 우측에 기둥이 있다면, 보를 삭제한 후 우측의 기둥이 온전히 있을 수 있는지 

bo[y][x] = false;
if((col[y][x] && !canBuildCol(x, y)) || 
	(col[y][x + 1] && !canBuildCol(x + 1, y)) || 
	(bo[y][x - 1] && ! canBuildBo(x - 1, y)) || 
	(bo[y][x + 1] && !canBuildBo(x + 1, y)) ){

	bo[y][x] = true;
}

온전히 있을 수 없다면 다시 보를 설치 합니다.

 

 

 마지막으로 정답을 출력하기 위한 정렬 순서로는 x좌표 순 오름차순, x좌표가 같다면 y좌표 순 오름차순, 둘 다 같다면 기둥이 먼저 출력이 되어야 합니다.

    for(int x=0;x<=n;x++){
        for(int y=0;y<=n;y++){
            if(col[y][x])
                answer.push_back({x,y,0});
            if(bo[y][x])
                answer.push_back({x,y,1});
        }    
    }
        

 

 

소스 코드


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

using namespace std;

bool bo[101][101];
bool col[101][101];

bool canBuildCol(int x, int y){
    return (y == 0 || col[y - 1][x] || bo[y][x] || bo[y][x - 1]);
}

bool canBuildBo(int x, int y){
    return (col[y - 1][x] || col[y - 1][x + 1] || (bo[y][x - 1] && bo[y][x + 1]));
}

void solve(vector<int> &info){
    
    int x = info[0];
    int y = info[1];
    int type = info[2];
    int build = info[3];
    
    if(type == 0 && build == 1){
        if(canBuildCol(x, y))
            col[y][x] = true;
    }
    else if(type == 1 && build == 1){
        if(canBuildBo(x, y))
            bo[y][x] = true;
    }
    else if(type == 0 && build == 0){
        col[y][x] = false;
        if((col[y + 1][x] && !canBuildCol(x, y+1)) || 
           (bo[y + 1][x] && !canBuildBo(x, y + 1)) || 
           (bo[y + 1][x - 1] && !canBuildBo(x - 1, y + 1))){
            
            col[y][x] = true;
        }
    }
    else{
        bo[y][x] = false;
        if((col[y][x] && !canBuildCol(x, y)) || 
           (col[y][x + 1] && !canBuildCol(x + 1, y)) || 
           (bo[y][x - 1] && ! canBuildBo(x - 1, y)) || 
           (bo[y][x + 1] && !canBuildBo(x + 1, y)) ){
            
            bo[y][x] = true;
        }
    }
}

vector<vector<int>> solution(int n, vector<vector<int>> build_frame) {
    
    vector<vector<int>> answer;
    
    memset(bo, false, sizeof(bo));
    memset(col, false, sizeof(col));
    
    for(int i=0;i<build_frame.size();i++)
        solve(build_frame[i]);
    
    for(int x=0;x<=n;x++){
        for(int y=0;y<=n;y++){
            if(col[y][x])
                answer.push_back({x,y,0});
            if(bo[y][x])
                answer.push_back({x,y,1});
        }    
    }
        
    return answer;
}

접근 방법


 

효율성 또한 판별하는 문제 이므로 빠른 실행 시간이 요구되는 방법을 찾아야 했습니다.

 일단 쿼리의 수가 매우 많기 때문에 매번 새로운 계산을 한다기 보다 적절하게 값을 저장한 뒤에 불러와야 한다는 점과 트라이 구조를 사용하면 해결할 수 있을 것이라는 아이디어가 있었습니다.

 

트라이 구조의 한 노드는 아래와 같은 자료구조를 갖습니다.

typedef struct node{
    map <char, node> child; 
    map <int, int> info;
};

1. child는 현재 문자에 따라 child 노드로 연결 할 수 있는 링크입니다.

2. info는 현재 info[idx를 포함하여 단어가 끝나기 까지의 문자 길이] = 갯수 입니다.

 

info를 조금 더 자세히 설명하자면 아래의 그림과 같습니다.

1번 노드의 경우 현재 자신부터 말단 까지

길이가 4인 것의 갯수는 2개([1,2,3,4], [1,2,3,5]) 

길이가 3인 것의 갯수는 1개([1,2,6])

길이가 2인 것의 갯수는 1개([1,7])이다.

2번 노드의 경우 현재 자신부터 말단 까지

길이가 3인 것의 갯수는 2개 ([2,3,4], [2,3,5])

길이가 2인 것의 갯수는 1개 ([2,6])이다. 

 

 이러한 구조를 사용하는 이유는 query의 한 단어에서 '?'가 나왔을 때 모든 노드를 탐색하지 않고 ?의 갯수를 통해 바로 매치되는 단어의 수를 찾을 수 있기 때문입니다. 

 

 예를 들어 위와 같은 그림에서 단어가 "1???"라면 1노드 방문 후 2번 노드로 이동할 것이고 이 때의 문자는 ?이며 '?'의 수는 3개 이므로 2번 노드의 info[3]을 조회하면 바로 매치되는 단어의 수를 찾을 수 있습니다. 

 

 마지막으로 생각해야 하는 부분은 '?'가 앞에서 나오는 경우 입니다. 예를 들어 "??abc"와 같은 경우인데 이런 경우는 문자의 가장 뒤 부터 트라이를 탐색해야 합니다. 이 부분을 해결하기 위해서는 트라이를 구성할 때 역방향의 트라이 또한 만들어야 합니다. 예를 들어 "abcd", "def"이런 단어가 주어진다면 아래 그림과 같은 역방향의 트라이도 같이 만듭니다.

 

위의 설명을 이해했다면 문제는 크게 2가지 과정을 통해 해결할 수 있습니다.

1. words를 통해 정방향, 역방향 트라이 만들기

2. query의 '?'가 앞에서 시작한다면 역방향 트라이로 탐색, '?'가 뒤에서 시작한다면 정방향 트라이로 탐색

 

소스 코드


#include <string>
#include <vector>
#include <map>

using namespace std;

typedef struct node{
    map <char, node> child; 
    map <int, int> info; 
};

void insert(int idx, node &trie, string &word, int dir){
//dir == 1 정방향, dir == -1 역방향
    
    if (dir == 1 && idx == word.size()) 
		return;
    if (dir == -1 && idx == -1)
        return;
    
    int left = (dir == 1) ? word.size() - idx : idx + 1; //자신을 포함한 나머지 길이
    
    if(trie.info.count(left) == 0)
            trie.info.insert({left, 1});       
    else
        trie.info[left]++;
        
	if(trie.child.count(word[idx]) == 0)
        trie.child[word[idx]] = node();
    
	insert(idx + dir, trie.child[word[idx]], word, dir);
}

int querying(int idx, node &trie, string &word, int dir){
//dir == 1 정방향, dir == -1 역방향
    
    if(dir == 1 && idx == word.size())
        return 1;
    if(dir == -1 && idx == -1)
        return 1;
        
    // '?'가 나온 경우 3항연산자를 사용하여 남은 '?'의 갯수를 알아내어 매칭되는 단어의 수 탐색
    if(word[idx] == '?'){
        int cnt = (dir == 1) ? word.size() - idx : idx + 1; 
        return trie.info[cnt];
    }
    
    //매칭되는 단어가 있는 경우
    //정방향이라면 dir == 1 이므로 idx가 1 증가, 역방향은 dir == -1이므로 idx 1감소
    if(trie.child.count(word[idx]) > 0)
        return querying(idx + dir, trie.child[word[idx]], word, dir);
    
    //매칭되는 단어가 없는 경우
    return 0;
}

vector<int> solution(vector<string> words, vector<string> queries) {
    
    vector<int> answer;
    
    node f_trie;
    node r_trie;
    
    for(int i=0;i<words.size();i++){
        string word = words[i];
        insert(0, f_trie, word, 1); //정방향 트라이
        insert(word.size() - 1, r_trie, word, -1); //역방향 트라이
    }
    
    for(int i=0;i<queries.size();i++){
        
        string query = queries[i];
        int ret = 0;
        
        if(query[0] != '?') // 쿼리의 뒤쪽에 '?'가 있는 경우 
            ret = querying(0, f_trie, query, 1);
        else               // 퀴리의 앞에 '?'가 있는 경우
            ret = querying(query.size() - 1, r_trie, query, -1);
        
        answer.push_back(ret);
    }
    
    return answer;
}

문제 링크


www.acmicpc.net/problem/20542

 

20542번: 받아쓰기

세계적인 기업 CTP(Chickens Threaten Programming)에 입사하기 위해서는 영어 받아쓰기 테스트를 통과해야 한다. 영어 받아쓰기는 채용 담당자가 불러주는 단어를 지원자가 받아쓰는 시험이다. CTP에서는

www.acmicpc.net

 

 

접근 방법


처음에 어떤 알고리즘을 적용해야 할지 몰라 문제를 못풀었습니다. 하지만 편집거리라는 알고리즘을 알고있다면 어렵지 않게 풀 수 있는 문제였습니다.

 편집거리 알고리즘은 LCS와 동작이 거의 비슷합니다. 하지만 차이점이 있다면 LCS는 일치하는 가장 긴 문자열을 찾아내기 위해 서로 일치하는 경우를 찾아 최댓값으로 갱신하였고, 편집거리는 가장 조금 수정하는 경우를 찾아내기 위해 서로 일치하지 않는 경우를 찾아 최소로 갱신한다는 차이가 있습니다.   

 

아래 블로그에 편집거리에 대한 자세한 설명이 있으니 참고 부탁드립니다.

hsp1116.tistory.com/41

 

편집 거리 알고리즘(Levenshtein Distance, Edit Distance)

편집 거리 알고리즘이란, 두 문자열의 유사도를 판단하는 알고리즘이다. 유사도를 판단하는 기준은, 어떠한 문자열을 삽입,삭제,변경을 몇 번이나 해서 바꿀수 있는지를 계산하여 그 최소값을

hsp1116.tistory.com

 

문제에서 유의해야 될 사항은 한 가지가 있습니다.

답안의 한 철자를 char g, 정답의 한 철자를 char a하고 한다면 

 g가 'i'일 때, a가 'i','j','l'이라면 정답처리를 하고,

 g가 'v'일 때, a가 'v','w'라면 정답처리를 한다는 것입니다.

 

즉, 편집거리 알고리즘을 진행하며 같은지 다른지에 대한 비교연산을 위와 같이 정의해서 풀어나가 문제를 해결할 수 있었습니다.

 

 

소스 코드


#include <iostream>
#include <vector>
#include <algorithm>
#include <string>

using namespace std;

int N, M;

string guess;
string ans;

bool isCorrect(char g, char a) {
	if (g == a)
		return true;
	else if (g == 'i' && (a == 'i' || a == 'j' || a == 'l'))
		return true;
	else if (g == 'v' && (a == 'v' || a == 'w'))
		return true;
	else
		return false;
}

int editDistance() {

	vector<vector<int>> dp(N + 1, vector<int>(M + 1, 0));

	for (int i = 1; i < N + 1; i++) //답안 
		dp[i][0] = i;
	for (int i = 1; i < M + 1; i++) //정답
		dp[0][i] = i;

	for (int i = 1; i < dp.size(); i++) { //답안
		for (int j = 1; j < dp[i].size(); j++) { //정답
			if (isCorrect(guess[i - 1], ans[j - 1])) //같다면 복제
				dp[i][j] = dp[i - 1][j - 1];
			else
				dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1; // 추가, 삭제, 수정 중 가장 적은 것
		}
	}
	return dp[N][M];
}

int main() {

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

	cin >> N >> M;

	for (int i = 0; i < N; i++) {
		char spell; cin >> spell;
		guess += spell;
	}
	for (int i = 0; i < M; i++) {
		char spell; cin >> spell;
		ans += spell;
	}
	cout << editDistance() << "\n";
}

문제 링크


www.acmicpc.net/problem/20543

 

20543번: 폭탄 던지는 태영이

시험을 망친 태영이가 인하대학교에 폭탄을 던진다! 인하대학교는 N×N 크기의 정사각형 모양의 땅이다. 인하대학교의 모든 땅은 1×1크기의 정사각형 칸으로 나누어져 있다. 각각의 칸은 (r, c)

www.acmicpc.net

 

접근 방법


 처음 이 문제를 접근하였을 때 시간초과를 해결하는 것이 어렵지 않았습니다. 하지만 풀이법을 떠올렸을 때 신선한 충격을 받게 되었습니다. 부분합을 적절히 활용하면 n^2의 복잡도로 해결할 수 있었습니다. 

 

만약 폭탄의 범위가 3인 경우 한 지역은 범위내에 있는 폭탄의 영향을 받습니다. 

 

x지점은 색이 칠해져 있는 부분들의 폭탄의 합과 같다는 것을 유추 할 수 있습니다.

그러면 한 지점의 폭탄의 수는 위의 정보를 활용하여 구할 수 있습니다.

 

문제에서 주어진 누적합의 배열을 sum[N], 각각 폭탄을 나타내는 배열을 bomb[N]이라 하였을 때

아래 그림의 정보를 활용하면 bomb[16]의 값은 다음과 같이 구할 수 있습니다.   

bomb[16] = 도형A(sum[11]) - 도형B(sum[7]) - 도형C(sum[10]) + 도형D(sum[6]) + bomb[4] + bomb[13] - bomb[1]

 

이 식을 문제에 맞게 일반화 시키면

(r = M/2일 때)

bomb[i][j] = sum[i-r][j-r] - sum[i-r-1][j-r] - sum[i-r][j-r-1] + sum[i-r-1][j-r-1] + bomb[i-M][j] + bomb[i][j-M] - bomb[i-M][j-M]

이와 같이 정의 할 수 있습니다.

 

소스 코드


#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

long long univ[2001][2001];
long long ans[2001][2001];
int N, M;

void printMap() {

	for (int i = 0; i < N; i++) {
		for (int j = 0; j < N; j++)
			cout << ans[i][j] << " ";
		cout << "\n";
	}
}

void solve() {
	
	int r = M / 2;
	int start = r;
	int end = N - r;
	             
	for (int i = start; i < end; i++) {
		for (int j = start; j < end; j++) {
			ans[i][j] = univ[i - r][j - r];
			if (i - r - 1 >= 0) 
				ans[i][j] -= univ[i - r - 1][j - r];
			if (j - r - 1 >= 0) 
				ans[i][j] -= univ[i - r][j - r - 1];
			if (i - r - 1 >= 0 && j - r - 1 >= 0) 
				ans[i][j] += univ[i - r - 1][j - r - 1];
			if (i - M >= 0)
				ans[i][j] += ans[i - M][j];
			if (j - M >= 0)
				ans[i][j] += ans[i][j - M];
			if (i - M >= 0 && j - M >= 0)
				ans[i][j] -= ans[i - M][j - M];
		}
	}
}

int main() {

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

	cin >> N >> M;

	for (int i = 0; i < N; i++) {
		for (int j = 0; j < N; j++) {
			long long num; cin >> num;
			univ[i][j] = -1 * num;
		}
	}

	solve();
	printMap();
}

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

[Java] 백준 21775 가희와 자원 놀이  (0) 2021.06.09
[C++] 백준 20542 받아쓰기  (0) 2021.01.15
[C++] 백준 19238 스타트 택시  (0) 2021.01.12
[C++] 백준 19237 어른 상어  (1) 2021.01.12
[C++] 백준 20544 공룡게임  (2) 2021.01.10

문제 링크


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);
}

 

문제 링크


문제 링크

www.acmicpc.net/problem/19237

 

19237번: 어른 상어

첫 줄에는 N, M, k가 주어진다. (2 ≤ N ≤ 20, 2 ≤ M ≤ N2, 1 ≤ k ≤ 1,000) 그 다음 줄부터 N개의 줄에 걸쳐 격자의 모습이 주어진다. 0은 빈칸이고, 0이 아닌 수 x는 x번 상어가 들어있는 칸을 의미

www.acmicpc.net

 

접근 방법


이 문제 또한 구현 및 시뮬레이션 문제였습니다. 

구현을 위해 두가지 구조체를 사용하였습니다. 

 

1. 상어의 방향의 우선순위를 정하기 위한 sharkInfo 입니다. 상어의 현재 진행 방향을 dir, dir에 따른 방향의 우선 순위를 지정하기위한 priority[dir][i]입니다.

typedef struct sharkInfo {
	int dir;
	vector<vector<int>> priority;
};

 

2. 또한 격자의 상태를 나타내기 위한 mapInfo 입니다. 어떤 상어의 냄새인지를 나타내는 num, 냄새가 사라지기 까지 남은 시간 time, 상어의 실제 존재 유무 exist입니다.

typedef struct mapInfo {
	int num, time;
	bool exist;
};

 

 

 이제 1초 동안 일어나는 일에 대해서 생각해 봐야 합니다. 저는 이러한 과정을 3가지로 나누어 생각했습니다.

1. 상어들이 이동할 다음 좌표를 알아낸다.

2. 1에서 찾은 좌표들로 상어들을 이동시킨다.(이 때 번호가 작은 상어가 번호가 큰 상어를 내 쫒음)

3. 전체의 격자에서 냄새가 사라지기 까지 남은 시간인 time을 갱신한다.

 

1. 한 상어의 좌표를 받아 상어의 번호와 다음 좌표를 반환하는 함수를 구현하였습니다.

일단 현재 냄새가 없는 곳으로 갈 수 있는지 탐색 후 그러한 공간이 없다면 자신의 냄새가 있던 곳을 탐색하게 됩니다.

이 때 sharks는 sharkInfo의 정보들을 담고 있는 전역의 벡터 입니다.

pair<int, pair<int, int>> findNextCoord(int x, int y) {

	int num = map[x][y].num;
	sharkInfo shark = sharks[num];
	int dir = shark.dir;

	int next_x = x;
	int next_y = y;
	
    //냄새가 없는 곳을 탐색
	for (int i = 0; i < 4; i++) {
		int nextdir = shark.priority[dir][i];
		next_x = x + direction[nextdir].first;
		next_y = y + direction[nextdir].second;
		if (canMove(next_x, next_y)) {
			sharks[num].dir = nextdir;
			break;
		}
	}
    //그러한 공간이 없다면 자신의 냄새가 있던 곳을 탐색
	if (!canMove(next_x, next_y)) {
		for (int i = 0; i < 4; i++) {
			int nextdir = shark.priority[dir][i];
			next_x = x + direction[nextdir].first;
			next_y = y + direction[nextdir].second;
			if (inRange(next_x, next_y)) {
				if (map[next_x][next_y].num == num) {
					sharks[num].dir = nextdir;
					break;
				}
			}
		}
	}

	return { num, {next_x, next_y} };
}

 

 2. 이동할 좌표에 어떤 냄새도 없다면 이동, 아니라면 자신과 번호가 같거나 큰 곳인 경우 이동

void moveOne(int num, int next_x, int next_y) {
	
	if (map[next_x][next_y].num > 0) { 
		if (map[next_x][next_y].num >= num) { //쫒아냄
			map[next_x][next_y] = { num, K, true };
		}
	}
	else {
		map[next_x][next_y] = { num, K, true };
	}
}

 

3. 현재 상어가 존재하는 곳을 제외한 나머지 공간에 냄새가 남아있다면 -1을 하고 그러한 시간이 0이 되었다면 해당 좌표에 남아있던 상어의 번호를 삭제

for (int i = 0; i < N; i++) {
   for (int j = 0; j < N; j++) {
    	if (!map[i][j].exist && map[i][j].time > 0) {
      		if (--map[i][j].time == 0)
      			map[i][j] = { 0, 0, false };
      	}
    }
}

 

 이러한 과정이 1초 동안 일어나는 일 입니다. 마지막으로 매 초마다 격자에 1번 상어만 남아있는지 검사를 하게 된다면 문제를 해결할 수 있습니다. 

 

 

소스 코드


#include <iostream>
#include <vector>

using namespace std;

typedef struct mapInfo {
	int num, time;
	bool exist;
};

typedef struct sharkInfo {
	int dir;
	vector<vector<int>> priority;
};

pair<int, int> direction[4] = { {-1,0}, {1,0}, {0,-1}, {0,1} };
mapInfo map[21][21];
vector<sharkInfo> sharks;
int N, M, K;

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

bool canMove(int x, int y) {
	if (inRange(x,y)) 
		if (map[x][y].time == 0) 
			return true;
	return false;
}

pair<int, pair<int, int>> findNextCoord(int x, int y) {

	int num = map[x][y].num;
	sharkInfo shark = sharks[num];
	int dir = shark.dir;

	int next_x = x;
	int next_y = y;

	for (int i = 0; i < 4; i++) {
		int nextdir = shark.priority[dir][i];
		next_x = x + direction[nextdir].first;
		next_y = y + direction[nextdir].second;
		if (canMove(next_x, next_y)) {
			sharks[num].dir = nextdir;
			break;
		}
	}
	if (!canMove(next_x, next_y)) {
		for (int i = 0; i < 4; i++) {
			int nextdir = shark.priority[dir][i];
			next_x = x + direction[nextdir].first;
			next_y = y + direction[nextdir].second;
			if (inRange(next_x, next_y)) {
				if (map[next_x][next_y].num == num) {
					sharks[num].dir = nextdir;
					break;
				}
			}
		}
	}

	return { num, {next_x, next_y} };
}

void moveOne(int num, int next_x, int next_y) {
	
	if (map[next_x][next_y].num > 0) { 
		if (map[next_x][next_y].num >= num) { //쫒아냄
			map[next_x][next_y] = { num, K, true };
		}
	}
	else {
		map[next_x][next_y] = { num, K, true };
	}
}

int solve() {

	int cnt = -1;

	while (++cnt <= 1000) {

		vector<pair<int, pair<int, int>>> numAndCoords;

		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				if (map[i][j].exist) {
					map[i][j].exist = false;
					numAndCoords.push_back(findNextCoord(i,j));
				}
			}
		}

		if (numAndCoords.size() == 1) 
			break;

		for (int i = 0; i < numAndCoords.size(); i++) {
			pair<int, pair<int, int>> elem = numAndCoords[i];
			moveOne(elem.first, elem.second.first, elem.second.second);
		}

		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				if (!map[i][j].exist && map[i][j].time > 0) {
					if (--map[i][j].time == 0)
						map[i][j] = { 0, 0, false };
				}
			}
		}
	}

	return cnt = (cnt > 1000 ? -1 : cnt);
}

int main() {

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

	cin >> N >> M >> K;

	sharks.resize(M + 1);

	for (int i = 0; i < N; i++) {
		for (int j = 0; j < N; j++) {
			int num; cin >> num;
			map[i][j].num = num;
			if (num > 0) {
				map[i][j].time = K;
				map[i][j].exist = true;
			}
		}
	}

	for (int i = 1; i <= M; i++) {
		cin >> sharks[i].dir;
		sharks[i].dir--;
	}
	
	for (int i = 1; i <= M; i++) {
		for (int j = 0; j < 4; j++) {
			vector<int> priority;
			for (int k = 0; k < 4; k++) {
				int dir; cin >> dir;
				priority.push_back(dir - 1);
			}
			sharks[i].priority.push_back(priority);
		}
	}

	cout << solve();
}

문제 링크


www.acmicpc.net/problem/20544

 

20544번: 공룡게임

크롬 브라우저 상에서 인터넷 연결이 안될때나, 주소창에 chrome://dino 를 입력하면 공룡 게임을 플레이 할 수 있다.

www.acmicpc.net

 

접근 방법

 


 해당문제는 4차원 DP를 사용하여 해결 할 수 있었습니다. 1차원 좌표계에서 4차원 DP를 생각해내는 것이 쉬운 일은 아니었던 문제였습니다.

 

DP의 1~4차원을 설명하자면

1. 현재 위치

2. 현재 높이(0,1,2) 

3. 과거 높이(0,1,2)

4. 높이가 2인 장애물 포함 여부(0,1)

 

 일단 문제를 처음 접근할 때 현재위치와 현재높이를 포함한 DP를 설계해야겠다고 생각했습니다. 이 후 과거의 높이가 다음의 높이 값에 영향을 줄 수 있는 변수라는 것을 알았습니다. 

예를 들어 현재의 값만 생각하였을 때 현재 상태가 1이라면 다음 상태는 0, 1, 2중 어떤 값이 와도 상관이 없습니다. 하지만 과거의 값이 1이었다면 다음 값은 0으로만 갈 수 있게 됩니다.

 

 또한 [현재위치][현재높이][과거높이]가 중복되는 값이 나오더라도 장애물 2를 포함해서 오지 않았다면 다른 경우의 수가 된다는 것을 떠올릴 수 있었습니다. 즉 DP에 장애물 2를 포함했는지의 여부를 나타내는 차원을 추가하였습니다.

 

소스 코드


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

using namespace std;
long long dp[1001][3][3][2];
long long mod = 1000000007;
int N;

long long solve(int now, int state, bool flag, int laststate) {

	long long &ref = dp[now][state][laststate][flag];

	if (now == N - 1) {
		if (flag)
			return ref = 1;
		else
			return ref;
	}	
	if (ref != 0)
		return ref;
	
	if (state == 0) {
		ref += solve(now + 1, 0, flag, state) % mod;
		ref += solve(now + 1, 1, flag, state) % mod;
		ref += solve(now + 1, 2, true, state) % mod;
	}
	else if (state == 1) {
		if (laststate == 0) {
			ref += solve(now + 1, 0, flag, state) % mod;
			ref += solve(now + 1, 1, flag, state) % mod;
			ref += solve(now + 1, 2, true, state) % mod;
		}
		else {
			ref += solve(now + 1, 0, flag, state) % mod;
		}
	}
	else if(state == 2){
		if (laststate == 0) {
			ref += solve(now + 1, 0, flag, state) % mod;
			ref += solve(now + 1, 1, flag, state) % mod;
		}
		else {
			ref += solve(now + 1, 0, flag, state) % mod;
		}
	}
	return ref = ref % mod;
}

int main() {

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

	cin >> N;

	cout << solve(0, 0, false, 0) << "\n";

}

 

+ Recent posts