이 포스팅은 Algorithm Problem Solving 시리즈 113 편 중 97 번째 글 입니다.

  • Part 1 - 백준(1003번): 피보나치 함수
  • Part 2 - 백준(1010번): 다리놓기
  • Part 3 - 백준(1012번): 유기농 배추
  • Part 4 - 백준(10159번): 저울
  • Part 5 - 백준(10164번): 격자상의 경로
  • Part 6 - 백준(1018번): 체스판 다시 칠하기
  • Part 7 - 백준(1018번): 체스판 다시 칠하기
  • Part 8 - 백준(1022번): 소용돌이 예쁘게 출력하기
  • Part 9 - 백준(10775번): 공항
  • Part 10 - 백준(10816번): 숫자 카드2
  • Part 11 - 백준(10816번): 숫자카드 2
  • Part 12 - 백준(10819번): 차이를 최대로
  • Part 13 - 백준(10830번): 행렬 곱셈
  • Part 14 - 백준(10844번): 쉬운 계단수
  • Part 15 - 백준(10868번): 최소값
  • Part 16 - 백준(1092번): 배
  • Part 17 - 백준(11003번): 최솟값 찾기
  • Part 18 - 백준(1102번): 발전소
  • Part 19 - 백준(11048번): 이동하기
  • Part 20 - 백준(11053번): 가장 긴 증가하는 부분 수열
  • Part 21 - 백준(11054번): 가장 긴 바이토닉 부분 수열
  • Part 22 - 백준(11055번): 가장 긴 증가하는 부분 수열2
  • Part 23 - 백준(11057번): 오르막 수
  • Part 24 - 백준(1120번): 문자열
  • Part 25 - 백준(11403번): 경로 찾기
  • Part 26 - 백준(11404번): 플루이드
  • Part 27 - 백준(1149번): RGB 거리
  • Part 28 - 백준(11559번): puyo puyo
  • Part 29 - 백준(11655번): ROT13
  • Part 30 - 백준(1167번): 트리의 지름
  • Part 31 - 백준(11722번): 가장 감소하는 부분 수열
  • Part 32 - 백준(12015번): 가장 긴 증가하는 부분 수열(LIS) 2
  • Part 33 - 백준(12851번): 숨바꼭질2
  • Part 34 - 백준(12852번): 1로 만들기 2
  • Part 35 - 백준(12865번): 평범한 배낭
  • Part 36 - 백준(1300번): K번째 수
  • Part 37 - 백준(1325번): 효율적인 해킹
  • Part 38 - 백준(13549번): 숨바꼭질3
  • Part 39 - 백준(13913번): 숨바꼭질4
  • Part 40 - 백준(14002번): 가장 긴 증가하는 부분 수열 4
  • Part 41 - 백준(1431번): 시리얼 넘버
  • Part 42 - 백준(1436번): 영화감독 숌
  • Part 43 - 백준(14499번): 주사위 굴리기
  • Part 44 - 백준(14888번): 연산자 끼워넣기
  • Part 45 - 백준(14889번): 스타트와 링크
  • Part 46 - 백준(14891번): 톱니바퀴
  • Part 47 - 백준(15658번): 연산자 끼워넣기 2
  • Part 48 - 백준(15686번): 치킨 배달
  • Part 49 - 백준(1697번): 숨바꼭질
  • Part 50 - 백준(1697번): 숨바꼭질
  • Part 51 - 백준(1707번): 이분 그래프(Bipartite Graph)
  • Part 52 - 백준(1708번): 볼록 껍질
  • Part 53 - 백준(17136번): 색종이 붙이기
  • Part 54 - 백준(1717번): 집합의 표현
  • Part 55 - 백준(17298번): 오큰수
  • Part 56 - 백준(17626번): Four Squares
  • Part 57 - 백준(18870번): 좌표 압축
  • Part 58 - 백준(1890번): 점프
  • Part 59 - 백준(1918번): 후위 표기식
  • Part 60 - 백준(1929번): 소수 구하기
  • Part 61 - 백준(1963번): 소수 경로
  • Part 62 - 백준(1965번): 상자넣기
  • Part 63 - 백준(1976번): 여행가자
  • Part 64 - 백준(1987번): 알파벳
  • Part 65 - 백준(1992번): 쿼드트리
  • Part 66 - 백준(2003번): 수들의 합2
  • Part 67 - 백준(20040번): 사이클 게임
  • Part 68 - 백준(2042번): 구간 합 구하기
  • Part 69 - 백준(2108번): 통계학
  • Part 70 - 백준(2110번): 공유기 설치
  • Part 71 - 백준(2156번): 포도주 시식
  • Part 72 - 백준(2193번): 이친수
  • Part 73 - 백준(2231번): 분해합
  • Part 74 - 백준(2250번): 트리의 높이와 너비
  • Part 75 - 백준(2293번): 동전 1
  • Part 76 - 백준(2294번): 동전 2
  • Part 77 - 백준(2343번): 기타 레슨
  • Part 78 - 백준(2468번): 안전 영역
  • Part 79 - 백준(2512번): 예산
  • Part 80 - 백준(2529번): 부등호
  • Part 81 - 백준(2565번): 전깃줄
  • Part 82 - 백준(2581번): 소수
  • Part 83 - 백준(2583번): 영역구하기
  • Part 84 - 백준(2630번): 색종이 만들기
  • Part 85 - 백준(2644번): 촌수계산
  • Part 86 - 백준(2667번): 단지번호붙이기
  • Part 87 - 백준(2751번): 수 정렬하기 2
  • Part 88 - 백준(2798번): 블랙잭
  • Part 89 - 백준(2904번): 수학은 너무 쉬워
  • Part 90 - 백준(2986번): 파스칼
  • Part 91 - 백준(3015번): 오아시스 재결합
  • Part 92 - 백준(3190번): 뱀
  • Part 93 - 백준(3190번): 벽 부수고 이동하기
  • Part 94 - 백준(4195번): 친구 네트워크
  • Part 95 - 백준(4948번): 베르트랑 공준
  • Part 96 - 백준(4963번): 섬의 개수
  • Part 97 - This Post
  • Part 98 - 백준(5373번): 큐빙
  • Part 99 - 백준(5437번): 불
  • Part 100 - 백준(6171번): 땅따먹기
  • Part 101 - 백준(6603번): 로또
  • Part 102 - 백준(6603번): 로또
  • Part 103 - 백준(7562번): 나이트의 이동
  • Part 104 - 백준(7568번): 덩치
  • Part 105 - 백준(7576번): 토마토
  • Part 106 - 백준(7579번): 앱
  • Part 107 - 백준(7785번): 회사에 있는 사람
  • Part 108 - 백준(9012번): 괄호
  • Part 109 - 백준(9020번): 골드바흐의 추측
  • Part 110 - 백준(9184번): 신나는 함수 실행
  • Part 111 - 백준(9251번): LCS
  • Part 112 - 백준(9421번): 소수상근수
  • Part 113 - 프로그래머스: 가장 큰 정사각형 찾기
▼ 목록 보기

골드3 : 그래프 문제이다.

생각

현재 시작 위치에서 다음 쓰레기를 치우고, 그 위치에서 다시 다음 경로를 탐색하는 과정으로 문제를 풀려 했다. 하지만 코드를 짜면서도 탐색과정이 계속하여 중복되서 어떻게 풀어야 할지 고민을 많이했다.

시간 초과 Code

#include<iostream>
#include<vector>
#include<algorithm>
#include<list>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<queue>
#include<functional>
#define INF 999999999
using namespace std;
typedef pair<int, int> PI;


int W, H;
char map[21][21];
int check[21][21];
int dy[4] = {0, -1, 0, 1}, dx[4] = {1, 0, -1, 0};

int ans;

vector<PI> trash;
PI start;

void printMap(){
    cout << "Map" << '\n';
    for (int i = 0; i < H; i++) {
        for (int j = 0; j < W; j++) {
            cout << map[i][j];
        }cout << '\n' << '\n';
    }
}

void printCheck(){
    cout << "Check" << '\n';
    for (int i = 0; i < H; i++) {
        for (int j = 0; j < W; j++) {
            cout << setw(3) <<check[i][j];
        }cout << '\n' << '\n';
    }
}

void bfs(PI start){
    memset(check, -1, sizeof(check));
    queue<pair<PI, int>> q;
    q.push(make_pair(start, 0));
    check[start.first][start.second] = 0;

    while (!q.empty()) {
        int now_y = q.front().first.first, now_x = q.front().first.second;
        int count = q.front().second;
        q.pop();

        count++;
        for (int i = 0; i < 4; i++) {
            int next_y = now_y + dy[i], next_x = now_x + dx[i];
            if (0 <= next_y && next_y < H && 0 <= next_x && next_x < W && map[next_y][next_x] != 'x') {
                if (check[next_y][next_x] == -1) {
                    q.push(make_pair(make_pair(next_y, next_x), count));
                    check[next_y][next_x] = count;
                }

            }
        }
    }
//    printCheck();
}


void go(int depth, PI start, int count){
//    printMap();
    if (depth == trash.size()) {
        ans = min(ans, count);
        return;
    }

//    memset(check, 0, sizeof(check));
    for (int i = 0; i < trash.size(); i++) {
        int now_y = trash[i].first, now_x = trash[i].second;
        if (map[now_y][now_x] == '*'){
            bfs(start);
            if (check[now_y][now_x] != 0) {
                map[start.first][start.second] = '.';
                map[now_y][now_x] = 'o';

                go(depth+1, trash[i], count+check[now_y][now_x]);
                map[now_y][now_x] = '*';
                map[start.first][start.second] = 'o';
            }
        }
    }
}

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

    while (true) {
        cin >> W >> H;
        ans = INF;
        trash.clear();
        if (W == 0 && H == 0) {
            return 0;
        }
        for (int i = 0; i < H; i++) {
            for (int j = 0; j < W; j++) {
                cin >> map[i][j];
                if (map[i][j] == 'o') {
                    start = make_pair(i, j);
                }
                if (map[i][j] == '*') {
                    trash.push_back(make_pair(i, j));
                }
            }
        }

        go(0, start, 0);

        if (ans == INF) {
            cout << -1 << '\n';
        } else {
            cout << ans << '\n';
        }
    }
}

그 결과 시간초과가 났다. 그래서 다른 방법을 고민했다.

그래프

o 역시 쓰레기로 본다면 다음과 같은 그림으로 볼 수 있다. image

결국, 0번 위치에서 시작하여 나머지 노드들을 전부 순회했을 때, 최소이동 거리를 구하는 문제이다. 이렇게 문제를 모델링할 경우 노드간의 거리를 구하는 과정을 1번만 수행하면 되기 때문에 위에 있는 코드보다 연산이 줄어든다.

7 5
.......
.o...*.
.......
.*...*.
.......
  0  4  2  6
  4  0  6  2
  2  6  0  4
  6  2  4  0

해당 입력에 대해 노드와 간선을 구하면 다음과 같이 구해진다. 이제 부터 해야할 일은 0번 부터 출발하여 어떤 순서로 이를 순회할지에 대한 문제이다.

  0  1  2  3
  0  1  3  2
  0  2  1  3
  0  2  3  1
  0  3  1  2
  0  3  2  1

순회하는 방법은 다음과 같이 순열로 구성된다. 0->1->2->3과 같은 방향으로 갔을 때, 이동거리의 최솟값을 구하면 된다.

Code

#include<iostream>
#include<vector>
#include<algorithm>
#include<list>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<queue>
#include<functional>
#define INF 999999999
using namespace std;
typedef pair<int, int> PI;

int N, M;
char map[21][21];
int check[21][21];
int node[11][11];
int dy[4] = {0, -1, 0, 1}, dx[4] = {1, 0, -1, 0};
vector<PI> trash;
int ans;

void printCheck(){
    cout << "Check" << '\n';
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            cout << setw(3) << check[i][j];
        }cout << '\n' << '\n';
    }
}

void printNode(){
    for (int i = 0; i < trash.size(); i++) {
        for (int j = 0; j < trash.size(); j++) {
            cout << setw(3) << node[i][j];
        }cout << '\n';
    }
}

void bfs(PI start){
    queue<PI> q;
    q.push(start);
    check[q.front().first][q.front().second] = 0;

    while (!q.empty()) {
        int now_y = q.front().first, now_x = q.front().second;
        q.pop();
        int count = check[now_y][now_x];

        for (int i = 0; i < 4; i++) {
            int next_y = now_y + dy[i], next_x = now_x + dx[i];
            if (0 <= next_y && next_y < N && 0 <= next_x && next_x < M && map[next_y][next_x] != 'x') {
                if (check[next_y][next_x] == -1) {
                    check[next_y][next_x] = count+1;
                    q.push(make_pair(next_y, next_x));
//                    printCheck();
                }
            }
        }
    }
}

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

    while (true) {
        cin >> M >> N;
        // 사용하는 배열 초기화
        trash.clear();
        ans = INF;
        memset(node, 0, sizeof(node));

        if (M == 0 && N == 0) return 0; // 종료 조건

        for (int i = 0; i < N; i++) {
            for (int j = 0; j < M; j++) {
                cin >> map[i][j];
                if (map[i][j] == 'o') trash.insert(trash.begin(), make_pair(i, j)); // 시작 위치는 0에 고정
                if (map[i][j] == '*') trash.push_back(make_pair(i, j));
            }
        }

        for (int i = 0; i < trash.size(); i++) {
            memset(check, -1, sizeof(check));
            bfs(trash[i]);
            for (int j = i; j < trash.size(); j++) {
                node[i][j] = check[trash[j].first][trash[j].second];
                node[j][i] = check[trash[j].first][trash[j].second];
            }
        }
//        printNode();

        vector<int> p; // 순열을 위한 배열 생성
        for (int i = 1; i < trash.size(); i++) p.push_back(i);

        do{
            p.insert(p.begin(), 0); // 시작 위치 추가
            int localAns = 0;
            for (int i = 0; i < p.size()-1; i++) {
                localAns += node[p[i]][p[i+1]];
                if (node[p[i]][p[i+1]] == -1) {
                    ans = -1;
                    break;
                }
            }
            ans = min(ans, localAns);
            p.erase(p.begin()); // 시작 위치 삭제
        }while(next_permutation(p.begin(), p.end()));

        cout << ans << '\n';
    }
    return 0;
}

Traveling Salesman problem (TSP)

이런 문제는 결국 모든 지점을 지나는데 걸리는 최소 거리를 묻는 문제이다. 이러한 문제는 Traveling Salesman problem (TSP) 라고 불리는 문제로 computer science 분야에서 가장 중요하게 취급되는 문제 중 하나이다. 비슷한 문제로는 백준(2098번) - 외판원 순회가 있다.

Reference

백준(4991번) - 로봇 청소기