https://www.acmicpc.net/problem/1446
이 문제를 풀었을때 의식의 흐름과 첫번째 정답을 도출한 과정을 먼저 설명하고자 한다.
우선 조합으로 모든 지름길을 사용할 수 있는 경우의 수를 모두 따진 다음 가장 최소의 값을 출력하는게 가장 처음 떠오른 생각이였다.
가장 시간이 많이 걸리기도 하고 N <= 12 였기 때문에 가능한 생각인거같아서 다른 방법도 생각해보았다.
두번째로는 다익스트라와 비슷한 방식으로 가장 길이를 많이 줄여주는 지름길을 먼저 택하는 방식이였는데 뭔가 반례가 무조건 있을꺼같았다.
세번째로 생각한 방법은 DP를 이용한 방법으로 dp[i][j]를 j지름길까지 고려했을 경우 i까지 갈 경우 최소시간을 생각했는데 이전에 사용한 지름길에 대한 정보를 줄 수 없으므로 겹치는 지름길을 판단하기 어렵다고 생각했다.
import java.io.*;
import java.util.*;
public class Main {
public static int N, D, ans;
public static int[][] road;
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
D = Integer.parseInt(st.nextToken());
road = new int[N][3];
ans = D;
for(int i = 0; i < N; i++){
st = new StringTokenizer(br.readLine());
road[i][0] = Integer.parseInt(st.nextToken());
road[i][1] = Integer.parseInt(st.nextToken());
road[i][2] = Integer.parseInt(st.nextToken());
}
dfs(0, 0, new ArrayList<Integer>());
System.out.println(ans);
}
public static void dfs(int at, int depth, List<Integer> list){
if(depth == N){
// for(int i = 0; i < list.size(); i++){
// System.out.print(list.get(i));
// }
// System.out.println();
int dis = toDistance(list);
ans = Math.min(dis, ans);
return;
}
for(int i = at; i < N; i++){
// 1. 이제까지 왔던 길과 겹치지 않고 D를 넘지 않으면
if(isOverlap(list, i)){
// 2. 리스트에 추가 후 다음
list.add(i);
dfs(i + 1, depth + 1, list);
for(int j = 0; j < list.size(); j++){
if(list.get(j) == i){
list.remove(j);
break;
}
}
}
dfs(i + 1, depth + 1, list);
}
}
public static int toDistance(List<Integer> list){
int dis = D;
for(int cur : list){
int from = road[cur][0];
int to = road[cur][1];
int value = road[cur][2];
dis = dis - (to - from) + value;
}
return dis;
}
public static boolean isOverlap(List<Integer> list, int now){
int from = road[now][0];
int to = road[now][1];
if(to > D) return false;
for(int cur : list){
int fromC = road[cur][0];
int toC = road[cur][1];
if(toC > D) return false;
if(to <= fromC || toC <= from) continue;
else return false;
}
return true;
}
}
앞에서는 이 문제를 풀기 위해 DFS로 가능한 모든 지름길 조합을 탐색하는 방식을 선택했다.
N ≤ 12라는 조건 때문에 완전탐색도 충분히 가능했고, 실제로 정답도 맞았다.
하지만 문제를 다시 보면서 몇 가지 찜찜한 점들이 있었다.
찜찜했던 이유는
- 지름길 겹침 여부를 직접 판단해야 한다.
isOverlap(), List add/remove, toDistance() 등의 부가적인 로직이 많이 필요했다. - 본질적으로 “지름길 조합 문제”처럼 보이지만, 사실은 “최단 거리 문제”라는 느낌이 들었다.
- 더 나은 방향이 있을 것 같았다.
DFS는 맞긴 맞는데, 뭔가 문제의 구조를 완전히 활용하지 못하고 힘으로 밀어붙이는 느낌이 들었다.
도로는 0km부터 Dkm까지 ‘직선’이다.
그리고 지름길은 이 직선 위의 특정 지점에서 특정 지점으로 가는 특수한 간선일 뿐이다.
즉,
0 ~ D까지 순서대로 진행하는 선형 그래프 + 중간에 몇 개의 추가 간선이 있는 DAG 구조다.
그러니 이 문제는 사실 이렇게 다시 정의된다.
0에서 D까지 가는 최소 비용을 구하는 최단거리 문제
여기서 중요한 건
“어떤 지름길 조합을 썼는지”가 아니라
“각 지점까지 최소 몇 km를 썼는지” 뿐이다.
DP 정의
dp[x] = 0에서 x 지점까지 이동하는 데 필요한 최소 거리
초기값: dp[0] = 0 나머지 dp[i] = 매우 큰 수
점화식
각 위치 i에서 선택할 수 있는 행동은 두 가지다.
1. 직선으로 한 칸 이동
dp[i+1] = min(dp[i+1], dp[i] + 1)
2. i에서 시작하는 모든 지름길 사용
지름길이 (i → end, cost)라면
dp[end] = min(dp[end], dp[i] + cost)
같이 고려했던 점
- end > D이면 지름길은 무시한다.
- to - from ≤ dist인 비효율적인 지름길도 무시해도 된다.
import java.io.*;
import java.util.*;
public class Main {
public static int N, D;
public static int[] dp;
public static List<int[]> list;
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
D = Integer.parseInt(st.nextToken());
list = new ArrayList<>();
for(int i = 0; i < N; i++){
st = new StringTokenizer(br.readLine());
int u = Integer.parseInt(st.nextToken());
int v = Integer.parseInt(st.nextToken());
int w = Integer.parseInt(st.nextToken());
if(v - u < w || v > D) continue;
list.add(new int[] {u, v, w});
}
list.sort((o1, o2) -> Integer.compare(o1[0], o2[0]));
int cnt = 0;
dp = new int[D + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for(int i = 0; i < D; i++){
dp[i + 1] = Math.min(dp[i + 1], dp[i] + 1);
for(int j = cnt; j < list.size(); j++){
int[] cur = list.get(j);
if(cur[0] == i && dp[cur[1]] > dp[i] + cur[2]){
dp[cur[1]] = dp[i] + cur[2];
cnt++;
}
}
}
System.out.println(dp[D]);
}
}
풀 수 있는것도 중요하지만 문제의 본질을 좀 더 살펴보고 문제를 풀어보자.
'코딩테스트 > JAVA' 카테고리의 다른 글
| [백준 JAVA] 1522번: 문자열 교환 (1) | 2025.12.15 |
|---|---|
| [백준 JAVA] 1515번: 수 이어쓰기 (0) | 2025.12.15 |
| [백준 JAVA] 1283번: 단축키 지정 (0) | 2025.12.05 |
| [백준 JAVA] 1238번: 파티 (0) | 2025.11.25 |
| 문자열 기초지식 보강 (0) | 2025.10.14 |