문제 <외판원 순회 2>의 실행시간이 오래 걸리는 이유는 모든 정점을 방문했는 지 확인하는 반복문과 이미 방문한 경로의 불필요한 탐색때문이다.
비트마스킹을 통해 이러한 반복을 줄여보자. 정점의 개수는 최대 16이다. 0번째, 1번째, ... 15번째를 방문했는지를 비트마스킹을 통해 00.....0 과 같이 길이가 16인 2진수로 표현할 수 있다. 그럼 $O(1)$만에 종료 조건을 확인할 수 있다.
하지만 모든 경우를 탐색하는 것은 똑같아서 수행시간에 큰 차이는 없을 것이다. (N배 빠르니까 약 10배)
여러 정점을 방문하면서 남은 정점을 확인할 때 중복되는 경우가 많다. 예를 들면 1 2 5 3 4 순으로 방문한 경우와 2 1 5 3 4 순으로 방문한 경우는 둘 다 1, 2를 방문한 상태에서 5 3 4 를 다시 방문하고 있다. 5 3 4 순으로 방문했을 때의 최적값을 구해둔다면 다시 방문할 필요 없이 미리 구한 최적해를 사용하면 된다. (메모이제이션으로 구현함)
이전에 작성한 글에서 소스의 다른 부분은 모든 정점을 방문했는 지 확인하는 반복문을 없애고 O(1)의 비트로 확인한다는 점과, 메모이제이션을 적용했다는 점 밖에 다르지 않다.
#include <bits/stdc++.h> using namespace std; #define INF 987654321 int dp[17][1<<17]; int g[17][17], n; int tsp(const int& first, int cur, int state, bool start=false){ if(!start && cur==first){ if( state == (1<<(n+1))-1 ) return 0; return INF; } int& r = dp[cur][state]; if(r != -1) return r; r = INF; for(int i=1; i<=n; ++i){ if(g[cur][i] > 0 && (state & (1<<i)) == 0){ r = min(r, g[cur][i] + tsp(first, i, state | (1<<i))); } } return r; } int main(){ int i, j; scanf("%d", &n); for(i=1; i<=n; ++i) for(j=1; j<=n; ++j) scanf("%d ", &g[i][j]); memset(dp, -1, sizeof(dp)); printf("%d", tsp(1, 1, 1, true)); return 0; }
댓글 없음:
댓글 쓰기