-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
41-gjsk132 #195
41-gjsk132 #195
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
악명 높은 외판원 순회... 저도 공부하고 나서야 이해가 되네요....
기본적으로 DP는 Stateless 하기 떄문에, 비트마스킹을 이용해 i번 도시에서 j번 도시로 가는 정보를 기억하고 사용할 수 있도록 하는 기법이 상당히 새롭네요...
제 경우에도 top-down approach가 직관적이어서 이렇게 풀어봤습니다.
전체 코드(파이썬)
input = open(0).readline
INF = 987654321
N = int(input())
W = [list(map(int, input().split())) for _ in range(N)]
dp = [[-1] * (1 << N) for _ in range(N)]
def TSP(i: int = 0, visited: int = 1) -> int:
if (1 << N) - 1 == visited:
if W[i][0] == 0:
return INF
else:
return W[i][0]
if dp[i][visited] > -1:
return dp[i][visited]
dp[i][visited] = INF
for j in range(N):
if W[i][j] == 0 or visited & (1 << j):
continue
dp[i][visited] = min(dp[i][visited], TSP(j, visited | (1 << j)) + W[i][j])
return dp[i][visited]
print(TSP())
전체 코드(c++)
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
constexpr int INF = 1e9;
int N, VisitedAllCities;
vector<vector<int>> W, DP;
inline bool HasVisitedAllCities(const int CurrentVisited)
{
return VisitedAllCities == CurrentVisited;
}
int TSP(int From = 0, int CurrentVisited = 1)
{
if(HasVisitedAllCities(CurrentVisited)) // 모든 노드 방문
{
if(W[From][0] == 0) return INF;
else return W[From][0];
}
else if(DP[From][CurrentVisited] > -1) // 중복
{
return DP[From][CurrentVisited];
}
else
{
DP[From][CurrentVisited] = INF; // 방문 Mark
for(int To = 0; To < N; ++To)
{
if(W[From][To] == 0 || (CurrentVisited & (1 << To))) continue;
int NextVisited = CurrentVisited | (1 << To);
DP[From][CurrentVisited] = min(DP[From][CurrentVisited], TSP(To, NextVisited) + W[From][To]);
}
return DP[From][CurrentVisited];
}
}
int main()
{
cin.tie(nullptr)->sync_with_stdio(false);
cin >> N;
W.assign(N, vector(N, 0));
VisitedAllCities = (1 << N) - 1;
for(int i = 0; i < N; ++i)
{
for(int j = 0; j < N; ++j)
{
cin >> W[i][j];
}
}
DP.assign(N, vector(VisitedAllCities, -1));
cout << TSP();
return 0;
}
bottom-up 방식의 풀이는 이 블로그가 해설이 괜찮아 보이네요 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Set으로 풀었다가 메모리 초과해서 당황했네요..ㅎ
어쩔 수 없이 비트마스킹을 제대로 학습하게 된 계기가 됐어요 ㅋㅋㅋ ㅠ
- (비트마스킹 학습한다고 하루 온종일 잡았네요 멍청한..)
import java.io.*;
import java.util.*;
public class Main {
static int N;
static final int INF = 16000000;
static int[][] W, dp;
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
N = Integer.parseInt(br.readLine());
W = new int[N][N];
for(int i = 0; i < N; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
for(int j = 0; j < N; j++) W[i][j] = Integer.parseInt(st.nextToken());
}
dp = new int[N][(1<<N)-1];
for(int i = 0; i < N; i++) Arrays.fill(dp[i], -1);
System.out.println(dfs(0, 1));
}
static int dfs(int now, int visit) {
if(visit == (1<<N)-1) {
if(W[now][0] == 0) return INF;
return W[now][0];
}
if(dp[now][visit] != -1) return dp[now][visit];
dp[now][visit] = INF;
for(int i=0;i<N;i++) {
if((visit & (1<<i)) == 0 && W[now][i] != 0) {
dp[now][visit] = Math.min(dfs(i, visit | (1 << i)) + W[now][i], dp[now][visit]); }
}
return dp[now][visit];
}
}
음 작성 중이던 PR을 2달만에 다시보니... 제가 뿌린 똥을 다시 맞는 기분이군요... |
@xxubin04 리뷰를 다셔야 합니다 😒 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2달 넘은 시점에서 리뷰 달아서 죄송합니다..😭😭
매번 직접 풀어보고 리뷰 달아야지 하다가 못 달고 이 사태가 일어났습니다..
'외판원 순회'라는 이름을 어디선가 들어본 듯 한데, 여기서 보니 신기하네요..
모든 경우의 수를 확인해야한다는 점에서 브루트포스로 풀면 되는구나 싶었는데 그건 또 시간초과가 나서 DP로 풀어주는 거군요.
꼭 풀어보고 싶었는데.. 다음에 꼭 풀어보겠습니다!!
🔗 문제 링크
백준2098 : 외판원 순회
✔️ 소요된 시간
2시간
✨ 수도 코드
🎯 문제 이해
해당 문제는 TSP의 가장 일반적인 형태의 문제이다.
TSP란?
모든 정점이 서로 연결되어 있고, 그 사이에 가중치를 가지는 완전 그래프가 주어진다.
그리고, 그래프의 출발 정점에서 모든 정점들을 방문하고, 원래의 출발 정점으로 되돌아오는 최소 비용(또는 경로)를 찾는 문제이다.
외판원 순회 Traveling Salesman problem (TSP) 문제로 조합 최적화 문제의 일종으로
NP-난해 집합에 속하는데...
쉽게 말해서, 어떠한 다항식을 정의해서 풀 수 있는게 아니라
모든 경우의 수를 확인해야지 해결할 수 있다고 한다.
사용하는 알고리즘
DP를 사용한다.
-> 현재 정점의 위치와 현재까지 방문한 정점을 이용하면 해당 문제를 해결할 수 있다.
여기서 추가로 현재까지 방문한 정점을 표현하는데 비트마스킹을 사용해주면 됩니다.
🔍코드 설명
📚 새롭게 알게된 내용
외판원 순회로 인해 굴러가는 스노우볼
이것저것 참고사이트
외판원 순회 개념
외판원 순회
NP-hard