알고리즘/문제 풀이

백준 15686번: 치킨 배달

Themion 2022. 1. 12. 15:11

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

 

15686번: 치킨 배달

크기가 N×N인 도시가 있다. 도시는 1×1크기의 칸으로 나누어져 있다. 도시의 각 칸은 빈 칸, 치킨집, 집 중 하나이다. 도시의 칸은 (r, c)와 같은 형태로 나타내고, r행 c열 또는 위에서부터 r번째 칸

www.acmicpc.net

치킨집을 m개 골랐을 때 도시의 치킨 거리가 dist_m이라면, 여기서 치킨집을 하나 더 추가했을 때 도시의 치킨 거리 dist_(m + 1)은 dist_m보다 작거나 같다. 따라서 구하고자 하는 값은 치킨집을 M개 골랐을 때의 도시의 치킨 거리의 최솟값이다.

도시의 치킨 거리를 최소화하기 위해 치킨집을 고르는 방법은 특별히 알려진 게 없으므로, 가능한 경우를 모두 확인해보아야 한다. 이 때 치킨집을 M개 골랐을 때마다 일일이 치킨 거리를 구하면 같은 치킨 거리를 여러번 구하게 되므로, 우선 각 집과 각 치킨집 사이의 치킨 거리를 구한 뒤 폐쇄하지 않을 치킨집을 고르는 것이 좋다.

i개의 치킨집을 골랐고 이 때 마지막으로 고른 치킨집이 k번째 치킨집이라면, 0번째부터 k번째 치킨집은 고려하지 않고 k + 1번째 치킨집부터 차례로 고른다. 이 때 M - i개의 치킨집을 한꺼번에 고르면 코드가 매우 복잡해지므로 재귀를 이용한 백트래킹을 통해 고르도록 한다.

#include <cstdio>

#define MAX_M 13
#define MAX_N 50
#define INF 0x3f3f3f3f
#define FOR(i, a, b) for (int i = a; i < b; i++)

// 좌표 { y, x }
struct p { int y = 0, x = 0; };

// dist[h][c]: h번째 치킨집과 c번째 집의 치킨 거리
char dist[MAX_M][2 * MAX_N];
// N: 도시의 크기, M: 남길 치킨집의 개수, h: 집의 수, c: 치킨집의 수
// arr: 남길 치킨집의 각 인덱스, ans: 도시의 치킨 거리의 최솟값
int N, M, h, c, arr[MAX_M], ans = INF;

int abs(int n) { return n < 0 ? -n : n; }
int min(int a, int b) { return a < b ? a : b; }

void backtrack(int len, int idx) {
    // d: 각 집의 최소 치킨 거리, sum: 도시의 치킨 거리
    int d, sum = 0;

    // 남길 치킨집이 정해졌다면
    if (len == M) {
        // 각 집에 대해
        FOR(i, 0, h) {
            // 치킨 거리를 INF로 초기화한 뒤 최소 치킨 거리를 계산
            d = INF;
            FOR(j, 0, len) d = min(d, dist[arr[j]][i]);
            // 현재 집의 치킨 거리를 sum에 더한다
            sum += d;
        }
        // 도시의 최소 치킨 거리를 sum과 비교해 갱신
        ans = min(ans, sum);
        return;
    }

    // 아직 남겨야 할 치킨집이 존재한다면
    FOR(i, idx, c) {
        // len번째로 남길 치킨집을 i번째 치킨집으로 정한 뒤 다음 치킨집을 선정
        arr[len] = i;
        backtrack(len + 1, i + 1);
    }
}

int main() {
    // 각 좌표의 정보를 입력받을 공간
    int buf;
    // home[h]: h번째 집의 좌표, chik[c]: c번째 치킨집의 좌표
    p home[2 * MAX_N], chik[MAX_M];

    // 도시의 크기와 남길 치킨집의 개수를 입력받은 뒤
    scanf("%d %d", &N, &M);
    // 각 좌표에 대해
    FOR(y, 0, N) FOR(x, 0, N) {
        // 해당 좌표의 정보를 입력받고 
        scanf("%d", &buf);
        // 해당 좌표가 집 혹은 치킨집이라면 각각 home, chik에 저장
        if (buf == 1) home[h++] = { y, x };
        if (buf == 2) chik[c++] = { y, x };
    }

    // 각 치킨집과 집의 치킨 거리를 dist에 저장
    FOR(i, 0, c) FOR(j, 0, h)
        dist[i][j] = abs(chik[i].y - home[j].y) + abs(chik[i].x - home[j].x);

    // 남겨야 할 치킨집을 계산한 뒤
    backtrack(0, 0);

    // M개의 치킨집을 남겼을 때의 최소 치킨 거리를 출력
    printf("%d\n", ans);

    return 0;
}