Skip to content

Commit

Permalink
5.22: dp, bitmasking, dfs(외판원 순회)
Browse files Browse the repository at this point in the history
  • Loading branch information
luckylooky2 committed May 22, 2024
1 parent 8d05438 commit d2e947e
Showing 1 changed file with 93 additions and 0 deletions.
93 changes: 93 additions & 0 deletions baekjoon/2098.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// 외판원 순회 : 동적 계획법, 깊이 우선 탐색, 비트마스킹
const input = require("fs")
.readFileSync("/dev/stdin")
.toString()
.trim()
.split("\n")
.map((v) => v.split(" ").map((v) => Number(v)));
const [n] = input.shift();
const map = input;
// -1 : 방문을 하지 않은 경로 또는 구조적으로 방문이 불가능한 경우(e.g. curr[1][13]의 경우 1101인데 curr에 해당하는 자리수가 0이기 때문에 구조적으로 불가)
const dp = new Array(n).fill(null).map(() => new Array(1 << n).fill(-1));
const stack = [];

function dfs(curr, visit) {
// 모든 노드를 전부 방문한 경우(visit : 1..1) : map[마지막 노드][0]을 반환해 첫 번째 노드로 돌아간다
if (visit === (1 << n) - 1) {
if (map[curr][0] === 0) {
// Infinity : 방문은 했지만, 길이 존재하지 않는 경우(map 값이 0인 경우)
return Infinity;
}
return map[curr][0];
}

// dp 테이블 캐싱
if (dp[curr][visit] !== -1) {
return dp[curr][visit];
}

// 방문 표시 및 Math.min에서 반드시 선택되지 않게 함
dp[curr][visit] = Infinity;

for (let i = 0; i < n; i++) {
// 길이 존재하지 않거나, 이미 방문한 노드인 경우 제외
if (map[curr][i] === 0 || (visit & (1 << i)) !== 0) {
continue;
}

stack.push(i + 1);
// 시작 노드(curr) -> i -> ... -> 1번 노드 경로의 최소값을 저장한다
dp[curr][visit] = Math.min(
dp[curr][visit],
map[curr][i] + dfs(i, visit | (1 << i))
);
stack.pop();
}

return dp[curr][visit];
}

const res = dfs(0, 1);
console.log(res);

// 풀지 못함

// 기본 구조 : dfs 완전 탐색 + dp
// - dfs만 사용하여 완전 탐색하면, 경우의 수 16!개로 시간 초과

// dp 테이블을 어떻게 이용하는가?
// - 1. 반복되는 지점이 어디인가?
// - 조건 1) 시작 지점이 같다 2) 끝 지점이 같다 3) 경유하는 지점들이 일치한다
// - **위 세 조건이 일치하면 저장해둔 최소값을 가져다 사용할 수 있음**
// - 위 풀이에서는 모든 경우에서 시작 노드와 끝 노드를 1로 고정한다고 가정
// - 큰 문제(e.g. 1 -> 2 -> 4 -> 3 -> 1과 1 -> 3 -> 2 -> 4 -> 1은 위 조건을 만족)와 작은 문제(e.g. 4 -> 2 -> 3 -> 1과 4 -> 3 -> 2 -> 1은 위 조건을 만족)에서 캐싱을 활용할 수 있다
// - 뒤에서 앞 방향으로 캐싱을 할 수 있고, dp 테이블이 같은 방향으로 채워진다

// - 2. dp 테이블을 어떻게 구성할 것인가?
// - n개의 행은, curr 값을 나타낸다
// - 2^n(1 << n)개의 열은, n개의 노드를 방문할 수 있는 경우의 수(0..0 ~ 1..1)의 각각을 나타낸다
// - dp 테이블의 값은 "시작 노드 번호가 curr + 1이고 끝 노드 번호가 1인, 인덱스를 2진수로 나타냈을 때 값이 0인 자리수 집합(즉, 노드 번호)을 모두 방문한 경로의 최소합"이다
// - curr을 행으로 따로 나눈 이유?
// - 같은 1101 (1, 3, 4번 노드)를 방문하는 경우에도 시작 노드가 다를 수 있기 때문에 경우의 수를 각각 저장한다. 시작 노드가 다르면 map을 참조하는 방법이 달라지기 때문이다

// e.g.
// 4
// 0 10 15 20
// 5 0 9 10
// 6 13 0 12
// 8 8 9 0

// dp 테이블
// -1 35 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
// -1 -1 -1 25 -1 -1 -1 18 -1 -1 -1 15 -1 -1 -1 -1
// -1 -1 -1 -1 -1 25 -1 20 -1 -1 -1 -1 -1 18 -1 -1
// -1 -1 -1 -1 -1 -1 -1 -1 -1 23 -1 15 -1 13 -1 -1

// dp[2][13] : 시작 노드 번호가 3(2 + 1)이고 끝 노드 번호가 1인, 인덱스를 2진수로 나타냈을 때(1101), 값이 0인 자리수 집합(노드 번호 : 2(1 + 1))을 모두 방문한 경로의 최소합
// - 대상 경로 : 3 -> 2 -> 1
// - 최소값 : 18 = 13(3 -> 2) + 5(2 -> 1)

// dp[1][3] : 시작 노드 번호가 2(1 + 1)이고 끝 노드 번호가 1인, 인덱스를 2진수로 나타내었을 때(0011), 값이 0인 자리수 집합(노드 번호 : 3(2 + 1), 4(3 + 1))을 모두 방문한 경로의 최소합
// - 대상 경로 : 2 -> (4, 3으로 만들 수 있는 모든 조합) -> 1
// - 최소값 : 25 = 10(2 -> 4) + 9(4 -> 3) + 6(3 -> 1)
// - 2 -> (4, 3으로 만들 수 있는 모든 조합) -> 1을 만족하는 모든 조합에서 이 최소값으로 캐싱이 가능하다

0 comments on commit d2e947e

Please sign in to comment.