Problem Solving/BOJ

외판원 순회 (Traveling Salesman Problem)

limdef 2023. 6. 20. 20:15

외판원 순회 문제 

- 여러 도시가 있고 도시 간 이동 비용이 있을 때 모든 도시를 한 번씩만 방문하여 시작 위치로 돌아오는 경로의 최소 비용을 구하는 문제.

 

각 도시가 20개라하면 20개의 도시를 모두 방문하는 경우의 수는 20! 이 된다.

모두 계산하지 않고  bitmask + memoization을 이용한다.

 

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

 

2098번: 외판원 순회

첫째 줄에 도시의 수 N이 주어진다. (2 ≤ N ≤ 16) 다음 N개의 줄에는 비용 행렬이 주어진다. 각 행렬의 성분은 1,000,000 이하의 양의 정수이며, 갈 수 없는 경우는 0이 주어진다. W[i][j]는 도시 i에서 j

www.acmicpc.net

 

왜 dp[1<<16] 가 아닌 dp[1<<16][16] 이 필요할까?

 

dp[1<<N] 만 사용한다고 해보자.

1,2,3번 3개의 도시가 있고 1->3 과 3->1 의 경로와 3->2 경로는 있지만 1->2 의 경로는 없다고 해보자.

 

먼저 1->3->2 의 경로를 탐색하고 dp[101]의 값을 갱신한다.

이후에 3->1 의 순서로 왔을 때 3->1->2 의 경로는 없지만 dp[101]의 값을 리턴하게 된다.

 

그래서 dp[101][3] 과 dp[101][1] 을 다르게 취급해줘야 한다.

( 현재 위치와 남은 도시의 집합이 같아야 계산을 안 해도 된다고 생각 ) 

 

풀이 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include<iostream>
#include<algorithm>
#include<cstring>
#define INF 987654321
using namespace std;
int n;
int cost[16][16];
int dp[1 << 16][16];
 
int dfs(int state, int here) {
    if (state == (1<<n)-1) {
        if (cost[here][0!= 0) {
            return cost[here][0];
        }
        else return INF;
    }
    int &ret = dp[state][here];
    if (ret != -1return ret;
 
    ret = INF;
 
 
    for (int i = 0;i < n;i++) {
        if (cost[here][i] == 0continue;
        if ((state&(1 << i)) == 0) {
            ret = min(ret, dfs(state | (1 << i), i) + cost[here][i]);
        }
    }
    
    return ret;
 
}
 
 
int main() {
    scanf("%d"&n);
    for (int i = 0;i < n;i++) {
        for (int j = 0;j < n;j++) {
            scanf("%d"&cost[i][j]);
        }
    }
 
    memset(dp, -1sizeof(dp));
    
    int ans=dfs(10);
 
    printf("%d", ans);
 
    return 0;
}
cs