백준 2098 외판원 순회
https://www.acmicpc.net/problem/2098
외판원 순회 문제입니다. 문제 설명에도 나와 있지만 TSP(Traveling Salesman Problem) 이라고 하여 굉장히 유명한 문제입니다.
시작 여행지만 지정해 준다면 우리는 DP를 이용하여 tsp를 구할 수 있습니다.
memoization을 할 배열은 [가장 마지막으로 방문한 노드][지금까지 방문한 노드의 정보] 형식을 갖습니다. (따라서 우리는 [가장 마지막으로 방문한 노드][모든 점을 방문]+distance[가장 마지막으로 방문한 노드][시작 노드] 의 최솟값을 구하면 됩니다.)
n이 16 이하이기 때문에 지금까지 방문한 노드의 정보를 bitmask를 이용하여 16자리 이진수 형태로 나타낼 수 있습니다. int 자료형이 31자리(부호 bit 제외) 인걸 감안하면 int형으로 나타내면 충분합니다.
(TSP는 복잡도가 $ n^2\times 2^n $ 인 NP problem 중에 하나로, 문제로 나온다면 이 문제처럼 n이 매우 작을 수 밖에 없습니다. 새로운 TSP 알고리즘이 나오거나 휴리스틱 문제가 아닌 이상 말입니다.)
이제 점화식을 살펴볼까요? 점화식은 간단합니다.
$ memo[x][vst]=min(memo[x][vst],distance[x][nxt]+memo[nxt][vst^(1«nxt]) $
의 형식을 갖습니다. 물론 nxt는 아직 방문하지 않은 점이여야만 합니다.
우리는 이제 시작점을 다르게 하면서 그 중에서 가장 작은 tsp를 구하는 방식으로 정답을 찾을 수 있습니다. 이렇게 하면 시간복잡도가 $ O(n^3\times2^n) $ 이 되겠네요.
아까 말한것과 다릅니다. 여기서, 발상을 조금만 더 전환한다면 아까 말한 복잡도로 시간을 줄이는 것이 가능해집니다.
외판원 순회에서는 시작 노드가 어디든지 딱히 상관이 없습니다. 어차피 우리가 구하는 것은 거리가 가장 짧은 원 모양의 경로를 구하는 것이기 때문이죠. 1에서 시작하든, 2에서 시작하든 결국 찾게 되는 경로는 동일할 겁니다.
끝났습니다. 0에서 출발하도록 tsp(0)을 출력해주면 정답이 됩니다.
참고로, unsigned int 배열에서 -1로 memset을 하게 되면 int형으로 형변환시 INT_MAX와 동일한 값을 가지게 됩니다.
#include<bits/stdc++.h>
using namespace std;
const int INF=INT_MAX;
const int SZ=18;
unsigned int memo[SZ][1<<SZ];
int graph[SZ][SZ];
int n;
int tsp(int stt){
memset(memo,-1,sizeof(memo));
memo[stt][1<<stt]=0;
for(int i=1<<stt;i<(1<<n);i++){
for(int j=0;j<n;j++){
if(!(i&(1<<j))) continue;
for(int k=0;k<n;k++){
if(i&(1<<k)) continue;
if(memo[j][i]<INF && graph[j][k] && memo[k][(i^(1<<k))]>memo[j][i]+graph[j][k]) memo[k][(i^(1<<k))]=memo[j][i]+graph[j][k];
}
}
}
int mn=INF;
for(int i=0;i<n;i++){
if(i==stt) continue;
if(graph[i][stt] && memo[i][(1<<n)-1]<INF) mn=min(mn,(int)memo[i][(1<<n)-1]+graph[i][stt]);
}
return mn;
}
int main(void){
ios::sync_with_stdio(false); cin.tie(NULL);
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++) cin>>graph[i][j];
}
cout<<tsp(0);
return 0;
}