(114)

[백준 17472번] 다리 만들기 2 - 그래프 탐색, 최소 신장 트리

문제 난이도GOLD I (링크)   문제 풀이각각의 독립된 섬들을 최단 거리로 연결하고 싶을 때, 놓을 수 있는 다리의 총 길이의 최소값을 구하는 문제.역시나 최소 신장 트리를 응용한 문제가 되겠다. 이번에는 각 간선을 구하는 것과 더불어, 이 섬이 어느 정점인지를 구하는 것도 필요하다. 결국엔 두 정점 사이의 간선을 구해서 문제를 풀어야하기 때문이다.  위의 그림처럼 문제에서 요한 3가지 올바르지 않은 방법과 함께, 추가로 두 가지만 주의한다면 쉽게 풀 수 있다. 처음 다른 섬을 만났을 때 만족하지 않는 조건이라면 바로 빠져나올 것.부끄럽게도 처음 코드를 완성시켰을 때,  다른 섬을 만났을 때, 종료 조건을 부족하게 작성하여 아래 그림처럼 더이상 탐색하지 못함에도 불구하고 탐색을 시켰다.   Union..

[백준 1774번, 백준 4386번] 우주선, 별자리 만들기 - 최소 신장 트리(MST)

문제 난이도GOLD III   1774번 풀이간선의 가중치가 주어지지 않아 간선의 가중치를 구하는 로직만 추가해준다면 쉽게 문제를 해결할 수 있다. 이 문제에서 간선의 가중치는 2차원에 있는 신들의 위치 사이의 거리를 구하는 것으로, 아래 공식처럼 유클리드 거리 계산 방법을 이용하면 쉽게 가중치를 구할 수 있다.   위 공식을 코드로 나타낸다면 아래와 같다. private static double calCost(int x1, int x2, int y1, int y2) {    return Math.sqrt(Math.pow(x1 - y1, 2) + Math.pow(x2 - y2, 2));}       크루스칼(Kruskal) 알고리즘 (with. 백준 1197번 최소 스패닝 트리)크루스칼 알고리즘 (Kru..

[백준 2568번 Java] 전깃줄 - 2

문제 난이도PLATINUM V (문제 링크)  풀이LIS 알고리즘을 이진 탐색을 통해 구현하면 되는 문제로 해당 문제를 어떻게 코드로 풀어낼 것인지에 대한 아이디어도 아래 포스팅에서 자세히 다루기 때문에 구체적인 설명은 필요하지 않을 것 같다. [알고리즘] LIS (Longest Increasing Subsequence)LIS(최장 증가 부분 수열)N크기의 배열이 주어졌을때, 배열에서 일부 원소를 조합하여 만든 부분 수열 중, 오름차순의 조건을 만족하면서 길이가 최대인 부분 수열을 말한다. 아래처럼, {3, 6, 2, 1,mag1c.tistory.com  간단하게 복기하자면, 해당 문제는 전깃줄의 개수가 최대 100,000개이기 때문에, DP로 LIS를 구현해서는 풀 수가 없다.  전깃줄의 시작지점을 오..

[백준 1918번 Java] 후위 표기식 - Stack

문제 난이도GOLD II (문제 링크)  풀이사칙연산에서의 연산 우선순위의 특징과 스택을 활용해서 풀 수 있다. 다들 알겠지만 리마인드하고 넘어가자면,1. 곱셈과 나눗셈은, 덧셈과 뺄셈보다 우선으로 연산된다.2. 괄호가 존재할 경우, 가장 먼저 처리된다.3. 우선순위가 같을 경우, 좌결합성의 특성에 따라 왼쪽의 수식을 우선처리한다.   또한 문제의 예제와, 그림표기된 것을 보고도 방법을 캐치할 수 있는데,   사칙 연산의 우선순위 특징을 리마인드했고, 어떻게 문제를 풀어나가야할지 통해 정리해볼 수 있다. 연산은 선출력하는 것이 아니라, 이전 연산자가 존재할 경우, 이전 연산과의 우선순위를 판별해서 출력 여부를 결정해야한다.괄호는 우선 연산이 되기때문에, 괄호 시작 부분을 만났을 경우에, 괄호가 끝나는 시..

[백준 1865번 Java] 웜홀 - 벨만 포드(Bellman Ford)

문제 난이도GOLD III (문제 링크)  풀이 [알고리즘] 벨만-포드(Bellman-Ford) 알고리즘벨만-포드 알고리즘은 그래프의 최단 경로를 구하는 알고리즘의 하나로, 다익스트라로 해결하지 못하는 음의 간선이 포함된 문제를 효과적으로 해결할 수 있는 알고리즘이다.  [알고리즘] 다mag1c.tistory.com 포스팅에서 학습했기 때문에 자세한 설명은 생략하고, 풀었던 방법만 나열해보고자 한다. 벨만-포드 알고리즘은 특정 출발점에서 모든 노드로의 최단 경로를 탐색하는 알고리즘이다. 또한 음수 사이클을 검출할 수 있다. 도달할 수 없는 노드를 표시하기 위해 INF에 가까운 값들로 초기화해서 사용할 수 있다.  첫번째 방법문제에서, 특정 노드를 언급하지 않았다. 한 노드에서 출발하여~~~이기 때문에, ..

[백준 1167번 Java] 트리의 지름 - DFS

문제 난이도GOLD II (문제 링크)   풀이두 노드 간의 최장 경로인 트리의 지름을 구하는 문제이다. 모든 경우의 수를 구해서, 가장 큰 값을 찾아야하기 때문에 DFS에 적합하다. DFS 구현은 단순하니 이를 통해 문제를 쉽게 해결할 수 있다.//dfs 호출부int max = 0;for (int i = 1; i     메모리 초과는 단순 이차원 배열을 통해 각 노드의 경우의 수를 계산했기 때문에 발생했다.이는 리스트로 구현하여 해결하였다.  하지만, 모든 경우의 수를 구하면 시간 초과가 발생하는데, 모든 경우의 수를 구하게 되면 O(V^2)이 되며, 문제 조건에서 V가 100,000까지이기 때문에 최적화할 수 있는 방법이 필요하다. 임의의 노드 x에서 최장 경로 노드를 DFS를 통해 구했고, 이를 y..

[백준 1238번 / Java] 파티 - 다익스트라(Dijkstra)

문제 난이도GOLD III  문제 링크https://www.acmicpc.net/problem/1238   풀이 [알고리즘] 다익스트라(Dijkstra) 알고리즘다익스트라(Dijkstra) 알고리즘그래프의 최단 경로를 구하는 알고리즘으로 하나의 정점에서 출발하여 최단 거리를 구하는 알고리즘이다. 탐욕법(Greedy)과 동적 계획법을 사용하는 알고리즘으로,mag1c.tistory.com 위의 다익스트라 알고리즘을 사용해서 구현한 문제이다.    문제 예시를 그림으로 표현해보았다.목적지로의 최단거리만 구하면 됐던 기본적인 다익스트라 알고리즘 구현 문제에서, 반대로 2에서 각 노드의 최단 경로를 한번 더 구해주기만 하면 된다. (A -> X, X -> A) private static int[] dijkstra..

[백준 1504번 / Java] 특정한 최단 경로 - 다익스트라(Dijkstra)

문제 링크 Gold 4 https://www.acmicpc.net/problem/1504 1504번: 특정한 최단 경로 첫째 줄에 정점의 개수 N과 간선의 개수 E가 주어진다. (2 ≤ N ≤ 800, 0 ≤ E ≤ 200,000) 둘째 줄부터 E개의 줄에 걸쳐서 세 개의 정수 a, b, c가 주어지는데, a번 정점에서 b번 정점까지 양방향 길이 존 www.acmicpc.net https://mag1c.tistory.com/452 다익스트라(Dijkstra) 알고리즘 다익스트라(Dijkstra) 알고리즘 그래프의 최단 경로를 구하는 알고리즘으로 하나의 정점에서 출발하여 최단 거리를 구하는 알고리즘이다. 탐욕법(Greedy)과 동적 계획법을 사용하는 알고리즘으로, mag1c.tistory.com 풀이 방향..

[백준 1916번 / Java] 최소비용 구하기 / 다익스트라(Dijkstra)

문제 링크 Gold 5 https://www.acmicpc.net/problem/1916 1916번: 최소비용 구하기 첫째 줄에 도시의 개수 N(1 ≤ N ≤ 1,000)이 주어지고 둘째 줄에는 버스의 개수 M(1 ≤ M ≤ 100,000)이 주어진다. 그리고 셋째 줄부터 M+2줄까지 다음과 같은 버스의 정보가 주어진다. 먼저 처음에는 그 www.acmicpc.net 첫 풀이 단순 BFS로 구해보려했던 것 같다. import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.*; public class Main { private static int N; private st..

[백준 17472번] 다리 만들기 2 - 그래프 탐색, 최소 신장 트리

P.S./백준 2024. 7. 20. 17:31
728x90
728x90

문제 난이도

GOLD I (링크)

 

 

 

문제 풀이

각각의 독립된 섬들을 최단 거리로 연결하고 싶을 때, 놓을 수 있는 다리의 총 길이의 최소값을 구하는 문제.

역시나 최소 신장 트리를 응용한 문제가 되겠다.

 

이번에는 각 간선을 구하는 것과 더불어, 이 섬이 어느 정점인지를 구하는 것도 필요하다.

 

결국엔 두 정점 사이의 간선을 구해서 문제를 풀어야하기 때문이다.

 

 

위의 그림처럼 문제에서 요한 3가지 올바르지 않은 방법과 함께, 추가로 두 가지만 주의한다면 쉽게 풀 수 있다.

 

처음 다른 섬을 만났을 때 만족하지 않는 조건이라면 바로 빠져나올 것.

부끄럽게도 처음 코드를 완성시켰을 때,  다른 섬을 만났을 때, 종료 조건을 부족하게 작성하여 아래 그림처럼 더이상 탐색하지 못함에도 불구하고 탐색을 시켰다.

 

 

 

Union Find가 끝난 후 모두 연결 되어있는지 확인할 것

이 부분은 Find를 한번 더 사용하면 쉽게 해결할 수 있다.

 

 

 

 

나는 이 두 가지 때문에 40분 정도 계속 틀렸다.

 

 

 

 

정리하자면

우리는 처음으로 만나는 같은 직선상의 다른 섬길이 2 이상의 다리를 놓을 수 있는 경우의 수를 판별하면 된다.

 

 

풀이 단계

나는 아래와 같은 순서로 문제를 풀었다.

 

 

1. 섬을 식별할 수 있는 번호를 부여해 줄 그래프 탐색(bfs)을 통해 각 정점을 구함

//islandCnt를 통해 정점을 표현하여
// islandsMap이라는 섬 지도에 연결된 섬인지를 표현

//기존 지도 map
int[][] map;

int islandCnt = 0;
int[][] islandsMap = new int[N][M];
boolean[][] isVisit = new boolean[N][M];
for (int i = 0; i < N; i++) {
    for (int j = 0; j < M; j++) {
        if (map[i][j] == 1 && !isVisit[i][j]) {
            islandCnt++;
            bfs(map, islandsMap, isVisit, N, M, i, j, islandCnt);
        }
    }
}


private static void bfs(int[][] map, int[][] islandsMap,
		boolean[][] isVisit, int N, int M, int x, int y, int cnt) {
    isVisit[x][y] = true;
    Queue<int[]> que = new LinkedList<>();
    que.offer(new int[] { x, y });
    //어느 정점인지를 표현
    islandsMap[x][y] = cnt;

    while (!que.isEmpty()) {
        int[] cur = que.poll();

        for (int i = 0; i < 4; i++) {
            int newX = cur[0] + dx[i];
            int newY = cur[1] + dy[i];

            if (isAvailableIdx(newX, newY, N, M) && !isVisit[newX][newY] && map[newX][newY] == 1) {
                isVisit[newX][newY] = true;
                islandsMap[newX][newY] = cnt;
                que.offer(new int[] { newX, newY });
            }

        }
    }
}

 

 

2. 연결 가능한 정점들끼리 연결

문제 조건에 맞게, 조건들을 설정하여 탐색을 통해 연결 가능한 섬들을 추출했다.

List<Bridge> bridges = findBridges(map, islandsMap, N, M);

private static List<Bridge> findBridges(int[][] map, int[][] islandsMap, int N, int M) {
    List<Bridge> bridges = new ArrayList<>();

    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            //1. 섬이 있을 경우에만 탐색
            if (islandsMap[i][j] > 0) {
                int number = islandsMap[i][j];

                //2. bfs와 유사하게 직선 방향으로만 탐색
                for (int k = 0; k < 4; k++) {
                    int length = 0;
                    int x = i;
                    int y = j;

                    while (true) {
                        x += dx[k];
                        y += dy[k];

                        //아래의 경우 탐색하지 않는다
                        //  3. 같은 정점(섬 그룹)에 속하거나
                        //  4. 길이가 1인 다른 섬의 경우
                        if (!isAvailableIdx(x, y, N, M) || islandsMap[x][y] == number
                                || (length <= 1 && map[x][y] == 1))
                            break;

                        if (islandsMap[x][y] > 0 && length >= 2) {
                            bridges.add(new Bridge(number, islandsMap[x][y], length));
                            break;
                        }
                        length++;
                    }
                }
            }
        }
    }

    return bridges;
}

 

 

3. 크루스칼 알고리즘을 통한 최소 신장 트리의 가중치를 구함

 Collections.sort(bridges, (o1, o2) -> o1.cost - o2.cost);

int[] parent = new int[islandCnt + 1];

for (int i = 0; i < islandCnt + 1; i++) {
    parent[i] = i;
}

int mstCost = 0;

for (Bridge b : bridges) {
    int s = b.start;
    int e = b.end;

    if (find(parent, s) != find(parent, e)) {
        union(parent, s, e);
        mstCost += b.cost;
    }
}

 

 

 

4. 모든 섬이 연결되었는지 확인

루트로 설정할 섬을 하나 정해서 설정한 뒤, Union Find의 Find를 돌려주면 된다.

int root = find(parent, 1);

for (int i = 2; i < islandCnt + 1; i ++) {
    if (find(parent, i) != root) {
        System.out.println(-1);
        return;
    }
}

 

 

 

 

전체 코드

public class Main {

    public static class Bridge {

        int start;
        int end;
        int cost;

        public Bridge(int start, int end, int cost) {
            this.start = start;
            this.end = end;
            this.cost = cost;
        }

    }

    private static int[] dx = { -1, 1, 0, 0 };
    private static int[] dy = { 0, 0, -1, 1 };

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        int N = Integer.parseInt(st.nextToken());
        int M = Integer.parseInt(st.nextToken());
        int[][] map = new int[N][M];

        for (int i = 0; i < N; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j < M; j++) {
                map[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        int islandCnt = 0;
        int[][] islandsMap = new int[N][M];
        boolean[][] isVisit = new boolean[N][M];
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < M; j++) {
                if (map[i][j] == 1 && !isVisit[i][j]) {
                    islandCnt++;
                    bfs(map, islandsMap, isVisit, N, M, i, j, islandCnt);
                }
            }
        }

        List<Bridge> bridges = findBridges(map, islandsMap, N, M);

        Collections.sort(bridges, (o1, o2) -> o1.cost - o2.cost);

        int[] parent = new int[islandCnt + 1];

        for (int i = 0; i < islandCnt + 1; i++) {
            parent[i] = i;
        }

        int mstCost = 0;

        for (Bridge b : bridges) {
            int s = b.start;
            int e = b.end;

            if (find(parent, s) != find(parent, e)) {
                union(parent, s, e);
                mstCost += b.cost;
            }
        }

        int root = find(parent, 1);

        for (int i = 2; i < islandCnt + 1; i ++) {
            if (find(parent, i) != root) {
                System.out.println(-1);
                return;
            }
        }

        System.out.println(mstCost);

    }

    private static int find(int[] parent, int x) {
        if (x == parent[x]) {
            return x;
        }

        return parent[x] = find(parent, parent[x]);
    }

    private static void union(int[] parent, int x, int y) {
        x = find(parent, x);
        y = find(parent, y);

        if (x == y)
            return;

        if (x < y) {
            parent[y] = x;
        } else {
            parent[x] = y;
        } 
    }

    private static List<Bridge> findBridges(int[][] map,
            int[][] islandsMap, int N, int M) {
        List<Bridge> bridges = new ArrayList<>();

        for (int i = 0; i < N; i++) {
            for (int j = 0; j < M; j++) {
                //1. 섬이 있을 경우에만 탐색
                if (islandsMap[i][j] > 0) {
                    int number = islandsMap[i][j];

                    //2. bfs와 유사하게 직선 방향으로만 탐색
                    for (int k = 0; k < 4; k++) {
                        int length = 0;
                        int x = i;
                        int y = j;

                        while (true) {
                            x += dx[k];
                            y += dy[k];

                            //아래의 경우 탐색하지 않는다
                            //  3. 같은 정점(섬 그룹)에 속하거나
                            //  4. 길이가 1인 다른 섬의 경우
                            if (!isAvailableIdx(x, y, N, M) || islandsMap[x][y] == number
                                    || (length <= 1 && map[x][y] == 1))
                                break;

                            if (islandsMap[x][y] > 0 && length >= 2) {
                                bridges.add(new Bridge(number, islandsMap[x][y], length));
                                break;
                            }
                            length++;
                        }
                    }
                }
            }
        }

        return bridges;
    }

    private static void bfs(int[][] map, int[][] islandsMap,
    		boolean[][] isVisit, int N, int M, int x, int y, int cnt) {
        isVisit[x][y] = true;
        Queue<int[]> que = new LinkedList<>();
        que.offer(new int[] { x, y });
        islandsMap[x][y] = cnt;

        while (!que.isEmpty()) {
            int[] cur = que.poll();

            for (int i = 0; i < 4; i++) {
                int newX = cur[0] + dx[i];
                int newY = cur[1] + dy[i];

                if (isAvailableIdx(newX, newY, N, M) && !isVisit[newX][newY] && map[newX][newY] == 1) {
                    isVisit[newX][newY] = true;
                    islandsMap[newX][newY] = cnt;
                    que.offer(new int[] { newX, newY });
                }

            }
        }
    }

    private static boolean isAvailableIdx(int x, int y, int N, int M) {
        return x >= 0 && x < N && y >= 0 && y < M;
    }

}
728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[백준 1774번, 백준 4386번] 우주선, 별자리 만들기 - 최소 신장 트리(MST)

P.S./백준 2024. 7. 19. 21:10
728x90
728x90

문제 난이도

GOLD III
 
 
 

1774번 풀이

간선의 가중치가 주어지지 않아 간선의 가중치를 구하는 로직만 추가해준다면 쉽게 문제를 해결할 수 있다.
 
이 문제에서 간선의 가중치는 2차원에 있는 신들의 위치 사이의 거리를 구하는 것으로, 아래 공식처럼 유클리드 거리 계산 방법을 이용하면 쉽게 가중치를 구할 수 있다.
 

 
 
위 공식을 코드로 나타낸다면 아래와 같다.
 

private static double calCost(int x1, int x2, int y1, int y2) {
    return Math.sqrt(Math.pow(x1 - y1, 2) + Math.pow(x2 - y2, 2));
}

 
 
 
 
 
 

 

크루스칼(Kruskal) 알고리즘 (with. 백준 1197번 최소 스패닝 트리)

크루스칼 알고리즘 (Kruskal Algorithm)크루스칼 알고리즘(Kruskal Algorithm)이란 최소 신장 트리(Minimum Spanning Tree)를 찾기 위해 사용되는 알고리즘이다. 신장 트리(Spanning Tree)그래프 내의 모든 정점을 포

mag1c.tistory.com

 
 
직전에 크루스칼 알고리즘에 대해 알아보았기 때문에 크루스칼 알고리즘으로 문제를 해결했다.
 
간선의 가중치를 위 공식으로 알아냈기 때문에, 간선을 기준으로 오름차순 정렬을 진행하여 Union-Find로 문제를 해결할 수 있다.
 
내가 문제를 풀이한 순서는 다음과 같다.
 

1. 입력 받기

입력을 Map형태로 받았다.
Key는 노드, Value는 해당 노드의 좌표를 받았다.

HashMap<Integer, int[]> map = new HashMap<>();

for (int i = 1; i <= N; i ++) {
    st = new StringTokenizer(br.readLine());
    int a = Integer.parseInt(st.nextToken());
    int b = Integer.parseInt(st.nextToken());
    map.put(i, new int[] {a, b});
}

 
 

2. Union-Find를 위한 각 노드의 부모 배열 생성 및 초기화

문제에서, 이미 연결된 각 노드들의 정보를 제공해 주었기에, union을 통해 부모 정보를 담고있는 배열을 업데이트 했다.

int[] parent = new int[N + 1];
//초기화
for (int i = 1; i <= N; i ++) {
    parent[i] = i;
}

//문제 조건 업데이트
for (int i = 0; i < M; i ++) {
    st = new StringTokenizer(br.readLine());
    int a = Integer.parseInt(st.nextToken());
    int b = Integer.parseInt(st.nextToken());
    union(parent, a, b);
}

 
 

3. 간선 가중치 계산

List<Node> lis = makeGraph(parent, N, map);

//신들의 그래프를 만든다.
// 이 때 find를 이용하여 연결되지 않은 신들의 가중치만을 계산한다.
// 이미 연결 되어있는 신들 간의 가중치는 계산할 필요가 없다.
private static List<Node> makeGraph(int[] parent, int N, HashMap<Integer, int[]> map) {
    List<Node> lis = new ArrayList<>();

    for (int i = 1; i <= N; i ++) {
        for (int j = 1; j <= N; j ++) {
            if (find(parent, i) != find(parent, j)) {
                double cost = calCost(map, i, j);
                lis.add(new Node(i, j, cost));
            }
        }
    }

    Collections.sort(lis, (o1, o2) -> Double.compare(o1.cost, o2.cost));

    return lis;
}


//가중치 계산
private static double calCost(HashMap<Integer, int[]> map, int x, int y) {
    int[] xLocation = map.get(x);
    int[] yLocation = map.get(y);

    return Math.sqrt(Math.pow(xLocation[0] - yLocation[0], 2) + Math.pow(xLocation[1] - yLocation[1], 2));
}

 
 
 

4. Union-Find를 이용해 문제 해결

마지막으로, 연결되지 않은 노드들의 가중치를 바탕으로 최소 신장 트리를 만들면서 문제에서 요구하는 가중치를 구한다.
요구조건에 맞게, 소숫점 두 번째 자리까지 출력한다.

List<Node> lis = makeGraph(parent, N, map);
double mst = 0;

for (Node n: lis) {
    int n1 = n.node1;
    int n2 = n.node2;

    if (find(parent, n1) != find(parent, n2)) {
        union(parent, n1, n2);
        mst += n.cost;
    }
}

System.out.printf("%.2f\n", mst);

 
 
 
 

전체 코드

public class Main {

    public static class Node {

        int node1;
        int node2;
        double cost;

        public Node(int n1, int n2, double c) {
            this.node1 = n1;
            this.node2 = n2;
            this.cost = c;
        }
        
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        StringTokenizer st = new StringTokenizer(br.readLine());
        int N = Integer.parseInt(st.nextToken());
        int M = Integer.parseInt(st.nextToken());

        HashMap<Integer, int[]> map = new HashMap<>();


        for (int i = 1; i <= N; i ++) {
            st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());
            map.put(i, new int[] {a, b});
        }

        int[] parent = new int[N + 1];

        for (int i = 1; i <= N; i ++) {
            parent[i] = i;
        }

        for (int i = 0; i < M; i ++) {
            st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());
            union(parent, a, b);
        }

        List<Node> lis = makeGraph(parent, N, map);
        double mst = 0;

        for (Node n: lis) {
            int n1 = n.node1;
            int n2 = n.node2;

            if (find(parent, n1) != find(parent, n2)) {
                union(parent, n1, n2);
                mst += n.cost;
            }
        }

        System.out.printf("%.2f\n", mst);

    }

    private static List<Node> makeGraph(int[] parent, int N, HashMap<Integer, int[]> map) {
        List<Node> lis = new ArrayList<>();

        for (int i = 1; i <= N; i ++) {
            for (int j = 1; j <= N; j ++) {
                if (find(parent, i) != find(parent, j)) {
                    double cost = calCost(map, i, j);
                    lis.add(new Node(i, j, cost));
                }
            }
        }

        Collections.sort(lis, (o1, o2) -> Double.compare(o1.cost, o2.cost));

        return lis;
    }

    private static double calCost(HashMap<Integer, int[]> map, int x, int y) {
        int[] xLocation = map.get(x);
        int[] yLocation = map.get(y);

        return Math.sqrt(Math.pow(xLocation[0] - yLocation[0], 2) + Math.pow(xLocation[1] - yLocation[1], 2));
    }

    private static int find(int[] parent, int x) {
        if (parent[x] == x) {
            return x;
        }

        return parent[x] = find(parent, parent[x]);
    }

    private static void union(int[] parent, int a, int b) {
        a = find(parent, a);
        b = find(parent, b);

        if (a == b) return;
        if (a < b) {
            parent[b] = a;
        }
        else {
            parent[a] = b;
        }
    }
}

 

 

 

 

 

 

 

4386번 풀이

위 풀이에서, 미리 Union을 수행하는 부분만 제외하면 똑같은 문제이다.

대신에 이미 이차원 평면에서 각 노드의 좌표를 double로 받아서 계산만 해주면 된다.

자세한 풀이는 1774번과 거의 흡사하니 위 풀이를 참고한다면 금방 풀이가 가능하다.

 

public class Main {

    public static class Node {

        int node1;
        int node2;
        double cost;

        public Node(int n1, int n2, double c) {
            this.node1 = n1;
            this.node2 = n2;
            this.cost = c;
        }
        
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());              

        HashMap<Integer, double[]> map = new HashMap<>();
        StringTokenizer st;

        for (int i = 1; i <= N; i ++) {
            st = new StringTokenizer(br.readLine());
            double x = Double.parseDouble(st.nextToken());
            double y = Double.parseDouble(st.nextToken());
            map.put(i, new double[] {x, y});
        }

        int[] parent = new int[N + 1];

        for (int i = 1; i <= N; i ++) {
            parent[i] = i;
        }

        List<Node> lis = makeGraph(parent, N, map);
        double mst = 0;

        for (Node n: lis) {
            int n1 = n.node1;
            int n2 = n.node2;

            if (find(parent, n1) != find(parent, n2)) {
                union(parent, n1, n2);
                mst += n.cost;
            }
        }

        System.out.printf("%.2f\n", mst);

    }

    private static List<Node> makeGraph(int[] parent, int N, HashMap<Integer, double[]> map) {
        List<Node> lis = new ArrayList<>();

        for (int i = 1; i <= N; i ++) {
            for (int j = 1; j <= N; j ++) {
                if (find(parent, i) != find(parent, j)) {
                    double cost = calCost(map, i, j);
                    lis.add(new Node(i, j, cost));
                }
            }
        }

        Collections.sort(lis, (o1, o2) -> Double.compare(o1.cost, o2.cost));

        return lis;
    }

    private static double calCost(HashMap<Integer, double[]> map, int x, int y) {
        double[] xLocation = map.get(x);
        double[] yLocation = map.get(y);

        return Math.sqrt(Math.pow(xLocation[0] - yLocation[0], 2) + Math.pow(xLocation[1] - yLocation[1], 2));
    }

    private static int find(int[] parent, int x) {
        if (parent[x] == x) {
            return x;
        }

        return parent[x] = find(parent, parent[x]);
    }

    private static void union(int[] parent, int a, int b) {
        a = find(parent, a);
        b = find(parent, b);

        if (a == b) return;
        if (a < b) {
            parent[b] = a;
        }
        else {
            parent[a] = b;
        }
    }
}
728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[백준 2568번 Java] 전깃줄 - 2

P.S./백준 2024. 6. 22. 08:50
728x90
728x90

문제 난이도

PLATINUM V (문제 링크)

 

 

풀이

LIS 알고리즘을 이진 탐색을 통해 구현하면 되는 문제로 해당 문제를 어떻게 코드로 풀어낼 것인지에 대한 아이디어도 아래 포스팅에서 자세히 다루기 때문에 구체적인 설명은 필요하지 않을 것 같다.

 

[알고리즘] LIS (Longest Increasing Subsequence)

LIS(최장 증가 부분 수열)N크기의 배열이 주어졌을때, 배열에서 일부 원소를 조합하여 만든 부분 수열 중, 오름차순의 조건을 만족하면서 길이가 최대인 부분 수열을 말한다. 아래처럼, {3, 6, 2, 1,

mag1c.tistory.com

 

 

간단하게 복기하자면, 해당 문제는 전깃줄의 개수가 최대 100,000개이기 때문에, DP로 LIS를 구현해서는 풀 수가 없다.

 

 

전깃줄의 시작지점을 오름차순으로 정렬하면, 전깃줄의 끝나는 지점이 오름차순일 때, 즉 부분 LIS 가 서로 교차하지 않는 전깃줄이 된다.

이진 탐색을 통해, 전깃줄의 끝나는 지점의 위치들을 찾아 이를 전깃줄이 시작하는 지점의 위치와 만족하는 경우를 찾으면 된다.

 

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.StringTokenizer;

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());
        int[][] line = new int[N][2];

        for (int i = 0; i < N; i ++) {
            //99%에서 오답이 나는 경우가 있어 입력 라인에 공백을 제거해야했다.
            StringTokenizer st = new StringTokenizer(br.readLine().trim());
            line[i][0] = Integer.parseInt(st.nextToken());
            line[i][1] = Integer.parseInt(st.nextToken());
        }

        Arrays.sort(line, (o1, o2) -> o1[0] - o2[0]);
        ArrayList<Integer> l = new ArrayList<>();
        int[] lIdx = new int[N];

        for (int i = 0; i < N; i ++) {
            lIdx[i] = binarySearch(l, line[i][1]);
        }

        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        int len = l.size();
        bw.write(N - len + "\n");

        HashSet<Integer> set = new HashSet<>();
        for (int i = N - 1, k = len - 1; i >= 0 && k >= 0; i--) {
            if (lIdx[i] == k) {
                set.add(line[i][1]);
                k--;
            }
        }

        for (int i = 0; i < N; i++) {
            if (!set.contains(line[i][1])) {
                bw.write(line[i][0] + "\n");
            }
        }

        bw.flush();
        bw.close();
    }

    private static int binarySearch(ArrayList<Integer> l, int endLine) {
        int start = 0;
        int end = l.size();

        while(start < end) {
            int mid = (start + end) / 2;

            if (l.get(mid) >= endLine) {
                end = mid;
            }
            else {
                start = mid + 1;
            }
        }

        if (start >= l.size()) {
            l.add(endLine);
        }
        else l.set(start, endLine);

        return start;
    }
}

 

 

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[백준 1918번 Java] 후위 표기식 - Stack

P.S./백준 2024. 6. 18. 22:12
728x90
728x90

문제 난이도

GOLD II (문제 링크)

 

 

풀이

사칙연산에서의 연산 우선순위의 특징과 스택을 활용해서 풀 수 있다.

 

다들 알겠지만 리마인드하고 넘어가자면,

1. 곱셈과 나눗셈은, 덧셈과 뺄셈보다 우선으로 연산된다.

2. 괄호가 존재할 경우, 가장 먼저 처리된다.

3. 우선순위가 같을 경우, 좌결합성의 특성에 따라 왼쪽의 수식을 우선처리한다.

출처: MDN

 

 

 

또한 문제의 예제와, 그림표기된 것을 보고도 방법을 캐치할 수 있는데,

 

 

 

사칙 연산의 우선순위 특징을 리마인드했고, 어떻게 문제를 풀어나가야할지 통해 정리해볼 수 있다.

 

  • 연산은 선출력하는 것이 아니라, 이전 연산자가 존재할 경우, 이전 연산과의 우선순위를 판별해서 출력 여부를 결정해야한다.
  • 괄호는 우선 연산이 되기때문에, 괄호 시작 부분을 만났을 경우에, 괄호가 끝나는 시점에서 괄호 시작부분까지의 모든 연산을 강제로 출력해주면 된다. 괄호가 중간중간에 있는 경우 앞서 설명한 대로, 연산자의 우선순위를 따르며 괄호 안의 연산을 우선적으로 처리한다.

 

직전 연산자과 비교할 수 있고, 계속해서 연산들을 저장하기 위해 스택을 활용해야한다.

스택을 활용해서 괄호 시작지점 또한 저장해서, 괄호가 언제 끝나는지 쉽게 알 수 있다. 또한 괄호 시작지점이 여러 개일 경우에도 처리가 가능하다.

 

 

문제의 해결방법을 생각했으니, 이제 코드로 나열해보자.

if (isOperator(cur)) {
    while (!stack.isEmpty() && precedence(stack.peek()) >= precedence(cur)) {
        bw.write(stack.pop());
    }
    stack.push(cur);
}


//연산자인지?
private static boolean isOperator(char c) {
    return c == '+' || c == '-' || c == '*' || c == '/';
}

//연산자 우선순위
private static int precedence(char op) {
    switch (op) {
        case '+': case '-':
            return 1;
        case '*': case '/':
            return 2;
    }
    return -1;
}

 

연산자일 경우, 선출력하는 것이 아니라 이전 연산자와의 비교를 거쳐 우선순위를 정해야한다.

 

 

else if (cur == '(') {
    stack.push(cur);
}
else if (cur == ')') {
    while(!stack.isEmpty() && stack.peek() != '(') {
        bw.write(stack.pop());
    }
    stack.pop();
}

 

괄호일경우, 스택을 통해 하나의 괄호가 온전히 처리될 수 있도록 한다. 괄호가 길어져도 연산자일 경우의 처리를 위에서 했기 때문에 신경쓰지 않아도 된다.

 

 

전체 코드

import java.io.*;
import java.util.Stack;

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String word = br.readLine();
        int len = word.length();
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        Stack<Character> stack = new Stack<>();


        for (int i = 0; i < len; i ++) {
            char cur = word.charAt(i);

            if (isOperator(cur)) {
                while (!stack.isEmpty() && precedence(stack.peek()) >= precedence(cur)) {
                    bw.write(stack.pop());
                }
                stack.push(cur);
            }
            else if (cur == '(') {
                stack.push(cur);
            }
            else if (cur == ')') {
                while(!stack.isEmpty() && stack.peek() != '(') {
                    bw.write(stack.pop());
                }
                stack.pop();
            }
            else bw.write(cur);

        }

        while(!stack.isEmpty()) {
            bw.write(stack.pop());
        }

        bw.flush();
        bw.close();
    }

    private static boolean isOperator(char c) {
        return c == '+' || c == '-' || c == '*' || c == '/';
    }

    private static int precedence(char op) {
        switch (op) {
            case '+': case '-':
                return 1;
            case '*': case '/':
                return 2;
        }
        return -1;
    }
}

 

 

 

 

 

참조

 

연산자 우선순위 - JavaScript | MDN

연산자 우선순위는 연산자를 실행하는 순서를 결정합니다. 우선순위가 높은 연산자가 먼저 실행됩니다.

developer.mozilla.org

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[백준 1865번 Java] 웜홀 - 벨만 포드(Bellman Ford)

P.S./백준 2024. 6. 17. 22:15
728x90
728x90

문제 난이도

GOLD III (문제 링크)

 

 

풀이

 

[알고리즘] 벨만-포드(Bellman-Ford) 알고리즘

벨만-포드 알고리즘은 그래프의 최단 경로를 구하는 알고리즘의 하나로, 다익스트라로 해결하지 못하는 음의 간선이 포함된 문제를 효과적으로 해결할 수 있는 알고리즘이다.  [알고리즘] 다

mag1c.tistory.com

 

포스팅에서 학습했기 때문에 자세한 설명은 생략하고, 풀었던 방법만 나열해보고자 한다.

 

벨만-포드 알고리즘은 특정 출발점에서 모든 노드로의 최단 경로를 탐색하는 알고리즘이다. 또한 음수 사이클을 검출할 수 있다. 도달할 수 없는 노드를 표시하기 위해 INF에 가까운 값들로 초기화해서 사용할 수 있다.

 

 

첫번째 방법

문제에서, 특정 노드를 언급하지 않았다. 한 노드에서 출발하여~~~이기 때문에, 모든 노드를 출발지점으로 삼고 릴렉세이션을 한다면 쉽게 문제 해결을 할 수 있다.

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main {

    private static class Edge {
        int from, to, cost;

        private Edge(int from, int to, int cost) {
            this.from = from;
            this.to = to;
            this.cost = cost;
        }
    }

    private static ArrayList<Edge> edgeList = new ArrayList<>();
    private static int[] dist;
    private static final int INF = Integer.MAX_VALUE;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        int TC = Integer.parseInt(br.readLine());

        StringTokenizer st;
        for (int i = 0; i < TC; i++) {
            st = new StringTokenizer(br.readLine());
            int N = Integer.parseInt(st.nextToken());
            int M = Integer.parseInt(st.nextToken());
            int W = Integer.parseInt(st.nextToken());

            dist = new int[N + 1];
            edgeList.clear();

            for (int j = 0; j < M; j++) {
                st = new StringTokenizer(br.readLine());
                int S = Integer.parseInt(st.nextToken());
                int E = Integer.parseInt(st.nextToken());
                int T = Integer.parseInt(st.nextToken());
                edgeList.add(new Edge(S, E, T));
                edgeList.add(new Edge(E, S, T));
            }
            for (int j = 0; j < W; j++) {
                st = new StringTokenizer(br.readLine());
                int S = Integer.parseInt(st.nextToken());
                int E = Integer.parseInt(st.nextToken());
                int T = Integer.parseInt(st.nextToken());
                edgeList.add(new Edge(S, E, -T));
            }

            boolean isYes = false;
            for (int j = 1; j <= N; j++) {
                if (bellmanFord(j, N)) {
                    bw.write("YES\n");
                    isYes = true;
                    break;
                }
            }

            if (!isYes) {
                bw.write("NO\n");
            }
        }

        bw.flush();
        bw.close();
    }

    private static boolean bellmanFord(int start, int N) {
        Arrays.fill(dist, INF);
        dist[start] = 0;

        // N-1번의 릴렉세이션(relaxation) 수행
        for (int i = 1; i < N; i++) {
            boolean updated = false;
            for (Edge edge : edgeList) {
                if (dist[edge.from] != INF && dist[edge.to] > dist[edge.from] + edge.cost) {
                    dist[edge.to] = dist[edge.from] + edge.cost;
                    updated = true;
                }
            }
            // 만약 이번 반복에서 업데이트가 없으면 종료
            if (!updated) break;
        }

        // 추가 릴렉세이션으로 음수 사이클 검증
        for (Edge edge : edgeList) {
            if (dist[edge.from] != INF && dist[edge.to] > dist[edge.from] + edge.cost) {
                return true; // 음수 사이클 존재
            }
        }

        return false;
    }
}

 

 

 

두번째 방법

첫 번째 방법처럼 모든 노드를 출발점으로 탐색하지 않아도, 음수 사이클 존재여부를 검출할 수 있는데, 음수 사이클이 존재하면 계속해서 전파된다는 특성을 이용해 접근이 가능하다. 우리는 음수 사이클 존재여부만 알면 된다.

 

이 때, 음수 사이클 존재 여부를 파악하기 위해 모든 간선을 검사해야 한다. 그러므로 기존 릴렉세이션을 사용하면 탐색되지 않은 경로에 음수 사이클이 있을 경우 놓칠 수 있다.

for (Edge edge : edgeList) {
    if (dist[edge.from] != INF && dist[edge.to] > dist[edge.from] + edge.cost) {
        dist[edge.to] = dist[edge.from] + edge.cost;
        updated = true;
    }
}

 

쉽게 말해, 위 코드를 아래처럼 변경해야 한다는 소리다.

for (Edge edge : edgeList) {
    if (dist[edge.to] > dist[edge.from] + edge.cost) {
        dist[edge.to] = dist[edge.from] + edge.cost;
        updated = true;
    }
}

 

 

모든 간선을 검사해야하기 때문에, INF가 오버플로우되어 -INF가 되는 것을 방지하기 위해 최대값을 조금 조정해주는 것이 필요하다.

 

모든 설명을 정리한 전체 코드는 아래와 같다.

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main {

    private static class Edge {
        int from, to, cost;

        private Edge(int from, int to, int cost) {
            this.from = from;
            this.to = to;
            this.cost = cost;
        }
    }

    private static ArrayList<Edge> edgeList = new ArrayList<>();
    private static int[] dist;
    static final int INF = 99999999;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        int TC = Integer.parseInt(br.readLine());

        StringTokenizer st;
        for (int i = 0; i < TC; i++) {
            st = new StringTokenizer(br.readLine());
            int N = Integer.parseInt(st.nextToken());
            int M = Integer.parseInt(st.nextToken());
            int W = Integer.parseInt(st.nextToken());

            dist = new int[N + 1];
            Arrays.fill(dist, INF);
            edgeList.clear();

            for (int j = 0; j < M; j++) {
                st = new StringTokenizer(br.readLine());
                int S = Integer.parseInt(st.nextToken());
                int E = Integer.parseInt(st.nextToken());
                int T = Integer.parseInt(st.nextToken());
                edgeList.add(new Edge(S, E, T));
                edgeList.add(new Edge(E, S, T));
            }
            for (int j = 0; j < W; j++) {
                st = new StringTokenizer(br.readLine());
                int S = Integer.parseInt(st.nextToken());
                int E = Integer.parseInt(st.nextToken());
                int T = Integer.parseInt(st.nextToken());
                edgeList.add(new Edge(S, E, -T));
            }

            if (bellmanFord(1, N)) {
                bw.write("YES\n");
            } else {
                bw.write("NO\n");
            }
        }

        bw.flush();
        bw.close();
    }

    private static boolean bellmanFord(int start, int N) {
        dist[start] = 0;

        for (int i = 1; i < N; i++) {
            for (Edge edge : edgeList) {
                if (dist[edge.to] > dist[edge.from] + edge.cost) {
                    dist[edge.to] = dist[edge.from] + edge.cost;
                }
            }
        }

        for (Edge edge : edgeList) {
            if (dist[edge.to] > dist[edge.from] + edge.cost) {
                return true;
            }
        }

        return false;
    }
}
728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[백준 1167번 Java] 트리의 지름 - DFS

P.S./백준 2024. 6. 11. 18:00
728x90
728x90

문제 난이도

GOLD II (문제 링크)

 

 

 

풀이

두 노드 간의 최장 경로인 트리의 지름을 구하는 문제이다.

 

모든 경우의 수를 구해서, 가장 큰 값을 찾아야하기 때문에 DFS에 적합하다. DFS 구현은 단순하니 이를 통해 문제를 쉽게 해결할 수 있다.

//dfs 호출부
int max = 0;
for (int i = 1; i <= V; i++) {
    boolean[] isVisit = new boolean[V + 1];
    max = Math.max(max, dfs(i, 0, isVisit));
}

//dfs 함수
 private static int dfs(int current, int dist, boolean[] isVisit) {
    isVisit[current] = true;
    int max = dist;

    for (Node node : tree.get(current)) {
        if (!isVisit[node.dest]) {
            max = Math.max(max, dfs(node.dest, dist + node.cost, isVisit));
        }
    }

    isVisit[current] = false;
    return max;
}

 

 

 

 

메모리 초과는 단순 이차원 배열을 통해 각 노드의 경우의 수를 계산했기 때문에 발생했다.

이는 리스트로 구현하여 해결하였다.

 

 

하지만, 모든 경우의 수를 구하면 시간 초과가 발생하는데, 모든 경우의 수를 구하게 되면 O(V^2)이 되며, 문제 조건에서 V가 100,000까지이기 때문에 최적화할 수 있는 방법이 필요하다.

 

임의의 노드 x에서 최장 경로 노드를 DFS를 통해 구했고, 이를 y라고 하자. 이 때 x의 최장 경로만을 구했기 때문에 시간 복잡도는 O(V)이다.

 

이제 x에서의 최장경로인 y에서의 최장경로를 다시 구해보자. 이 때 두 노드의 경로는 반드시 최장 경로가 될 수 밖에 없다. x을 기준으로 y를 찾아내면, y는 트리의 한쪽 끝에 위치하기 때문이다. 트리의 한쪽 끝에 위치한 y에서의 최장 경로가 지름이 될 수밖에 없다. 그리고 두번의 DFS만을 사용했기 때문에 여전히 O(V)이다.

 

이제 정리는 끝났으니, DFS 호출부와 구현부를 변경해보자.

//farthestNode : 임의의 노드(여기서는 1)에서의 가장 거리가 먼 노드
isVisit = new boolean[V + 1];
maxDistance = 0;
dfs(1, 0);

isVisit = new boolean[V + 1];
maxDistance = 0;
dfs(farthestNode, 0);


// dfs 함수
private static int dfs(int current, int dist, boolean[] isVisit) {
    isVisit[current] = true;
    int max = dist;

    for (Node node : tree.get(current)) {
        if (!isVisit[node.dest]) {
            max = Math.max(max, dfs(node.dest, dist + node.cost, isVisit));
        }
    }

    isVisit[current] = false;
    return max;
}

 

 

최적화를 통해, 단순 하나의 노드의 최장거리 계산을 DFS를 통해 단 두번만 호출하게 되어, O(V^2)이 O(V)가 되었다.

 

 

 

전체 코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    static class Node {
        int dest;
        int cost;

        public Node(int dest, int cost) {
            this.dest = dest;
            this.cost = cost;
        }
    }

    private static List<List<Node>> tree;
    private static int V;
    private static boolean[] isVisit;
    private static int maxDistance;
    private static int farthestNode;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        V = Integer.parseInt(br.readLine());
        StringTokenizer st;

        tree = new ArrayList<>();
        for (int i = 0; i <= V; i++) {
            tree.add(new ArrayList<>());
        }

        for (int i = 0; i < V; i++) {
            st = new StringTokenizer(br.readLine());
            int start = Integer.parseInt(st.nextToken());

            while (true) {
                int node = Integer.parseInt(st.nextToken());
                if (node == -1) break;
                int cost = Integer.parseInt(st.nextToken());
                tree.get(start).add(new Node(node, cost));
            }
        }

        isVisit = new boolean[V + 1];
        maxDistance = 0;
        dfs(1, 0);

        isVisit = new boolean[V + 1];
        maxDistance = 0;
        dfs(farthestNode, 0);

        System.out.println(maxDistance);
    }

    private static void dfs(int current, int dist) {
        isVisit[current] = true;

        if (dist > maxDistance) {
            maxDistance = dist;
            farthestNode = current;
        }

        for (Node node : tree.get(current)) {
            if (!isVisit[node.dest]) {
                dfs(node.dest, dist + node.cost);
            }
        }
    }
}
728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[백준 1238번 / Java] 파티 - 다익스트라(Dijkstra)

P.S./백준 2024. 6. 9. 17:23
728x90
728x90

 

 

문제 난이도

GOLD III

 

 

문제 링크

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

 

 

 

풀이

 

[알고리즘] 다익스트라(Dijkstra) 알고리즘

다익스트라(Dijkstra) 알고리즘그래프의 최단 경로를 구하는 알고리즘으로 하나의 정점에서 출발하여 최단 거리를 구하는 알고리즘이다. 탐욕법(Greedy)과 동적 계획법을 사용하는 알고리즘으로,

mag1c.tistory.com

 

위의 다익스트라 알고리즘을 사용해서 구현한 문제이다.

 

 

 

 

문제 예시를 그림으로 표현해보았다.

목적지로의 최단거리만 구하면 됐던 기본적인 다익스트라 알고리즘 구현 문제에서, 반대로 2에서 각 노드의 최단 경로를 한번 더 구해주기만 하면 된다. (A -> X, X -> A)

 

private static int[] dijkstra(List<List<Node>> graph, int n, int start) {
    PriorityQueue<Node> pq = new PriorityQueue<>();
    int[] dist = new int[n + 1];
    Arrays.fill(dist, Integer.MAX_VALUE);
    boolean[] visited = new boolean[n + 1];

    dist[start] = 0;
    pq.offer(new Node(start, 0));

    while (!pq.isEmpty()) {
        Node current = pq.poll();
        int curNode = current.node;

        if (visited[curNode]) continue;
        visited[curNode] = true;

        for (Node neighbor : graph.get(curNode)) {
            if (dist[neighbor.node] > dist[curNode] + neighbor.cost) {
                dist[neighbor.node] = dist[curNode] + neighbor.cost;
                pq.offer(new Node(neighbor.node, dist[neighbor.node]));
            }
        }
    }

    return dist;
}

 

기본적인 다익스트라 구현 코드이다. 이를 문제에 맞게, X로 가는 최단거리를 구할 때, X에서 각 노드로 가는 최단거리를 구할 때, 총 두 번 사용해서 문제를 해결하면 된다.

 

List<List<Node>> city = new ArrayList<>();
for (int i = 0; i <= N; i ++) {
    city.add(new ArrayList<>());
}

for (int i = 0; i < M; i ++) {
    st = new StringTokenizer(br.readLine());
    int start = Integer.parseInt(st.nextToken());
    int dest = Integer.parseInt(st.nextToken());
    int cost = Integer.parseInt(st.nextToken());

    city.get(start).add(new Node(dest, cost));
}

List<List<Node>> reverseCity = new ArrayList<>();
for (int i = 0; i <= N; i++) {
    reverseCity.add(new ArrayList<>());
}
for (int i = 1; i <= N; i++) {
    for (Node node : city.get(i)) {
        reverseCity.get(node.node).add(new Node(i, node.cost));
    }
}

 

왕복 경로를 계산하기 위해 기존의 그래프의 구현 리스트와, 역방향 그래프를 따로 만들었다.

원래 그래프에서 1....N번째에서 출발하여 X로 가는 최단거리를 계산하고, 역방향 그래프에서 X에서 출발하여 각 노드로 가는 최단 거리를 계산할 수 있다.

 

 

package bakjoon;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    static class Node implements Comparable<Node> {
        int node;
        int cost;
        public Node (int node, int cost) {
            this.node = node;
            this.cost = cost;
        }

        @Override
        public int compareTo(Node o) {
            return cost - o.cost;
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int N = Integer.parseInt(st.nextToken());
        int M = Integer.parseInt(st.nextToken());
        int X = Integer.parseInt(st.nextToken());

        List<List<Node>> city = new ArrayList<>();
        for (int i = 0; i <= N; i++) {
            city.add(new ArrayList<>());
        }

        for (int i = 0; i < M; i++) {
            st = new StringTokenizer(br.readLine());
            int start = Integer.parseInt(st.nextToken());
            int dest = Integer.parseInt(st.nextToken());
            int cost = Integer.parseInt(st.nextToken());

            city.get(start).add(new Node(dest, cost));
        }

        int[] toX = dijkstra(city, N, X);

        List<List<Node>> reverseCity = new ArrayList<>();
        for (int i = 0; i <= N; i++) {
            reverseCity.add(new ArrayList<>());
        }
        for (int i = 1; i <= N; i++) {
            for (Node node : city.get(i)) {
                reverseCity.get(node.node).add(new Node(i, node.cost));
            }
        }

        int[] fromX = dijkstra(reverseCity, N, X);

        int max = 0;
        for (int i = 1; i <= N; i++) {
            max = Math.max(max, toX[i] + fromX[i]);
        }

        System.out.println(max);
    }

    private static int[] dijkstra(List<List<Node>> graph, int n, int start) {
        PriorityQueue<Node> pq = new PriorityQueue<>();
        int[] dist = new int[n + 1];
        Arrays.fill(dist, Integer.MAX_VALUE);
        boolean[] visited = new boolean[n + 1];

        dist[start] = 0;
        pq.offer(new Node(start, 0));

        while (!pq.isEmpty()) {
            Node current = pq.poll();
            int curNode = current.node;

            if (visited[curNode]) continue;
            visited[curNode] = true;

            for (Node neighbor : graph.get(curNode)) {
                if (dist[neighbor.node] > dist[curNode] + neighbor.cost) {
                    dist[neighbor.node] = dist[curNode] + neighbor.cost;
                    pq.offer(new Node(neighbor.node, dist[neighbor.node]));
                }
            }
        }

        return dist;
    }
}
728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[백준 1504번 / Java] 특정한 최단 경로 - 다익스트라(Dijkstra)

P.S./백준 2023. 8. 14. 06:21
728x90
728x90

문제 링크

Gold 4

 

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

 

1504번: 특정한 최단 경로

첫째 줄에 정점의 개수 N과 간선의 개수 E가 주어진다. (2 ≤ N ≤ 800, 0 ≤ E ≤ 200,000) 둘째 줄부터 E개의 줄에 걸쳐서 세 개의 정수 a, b, c가 주어지는데, a번 정점에서 b번 정점까지 양방향 길이 존

www.acmicpc.net

 

https://mag1c.tistory.com/452

 

다익스트라(Dijkstra) 알고리즘

다익스트라(Dijkstra) 알고리즘 그래프의 최단 경로를 구하는 알고리즘으로 하나의 정점에서 출발하여 최단 거리를 구하는 알고리즘이다. 탐욕법(Greedy)과 동적 계획법을 사용하는 알고리즘으로,

mag1c.tistory.com

 

 

풀이

방향성이 없는 그래프가 주어졌으며, 이는 양방향으로 구현해야 함

또한 주어진 두 가지의 경로 v1, v2를 모두 통과한 후 최종 지점으로 도착할 수 있는 경로를 구해야한다는 점이다.

 

1번에서 출발해서 v1, v2를 거쳐 최종 목적지로 도착하는 방법은 두 가지가 있으며 이를 쪼개면 다음과 같이 표현할 수 있다.

1 → v1 → v2 → goal
// 1 → v1
// v1 → v2
// v2 → goal

1 → v2 → v1 → goal
// 1 → v2
// v2 → v1
// v1 → goal

 

위에서 종합한 조건들을 바탕으로 기본 다익스트라 알고리즘을 구현한 코드 위에 아래 세 가지만 고려하여 작성하면 된다.

 

1. 양방향으로 구현하기 때문에 반대의 경우도 노드 리스트에 추가

for(int i = 0; i < E; i ++) {
    st = new StringTokenizer(br.readLine(), " ");
    int before = Integer.parseInt(st.nextToken());
    int after = Integer.parseInt(st.nextToken());
    int weight = Integer.parseInt(st.nextToken());

    //단방향
    nodeList.get(before).add(new Node(after, weight));
    
    //양방향
    nodeList.get(after).add(new Node(before, weight));
}

 

2. 파라미터로 각 조건을 분할해서 수행하면 코드의 재사용성을 극대화할 수 있음

int distV1 = 0;
distV1 += dist(1, v1);
distV1 += dist(v1, v2);
distV1 += dist(v2, N);

int distV2 = 0;
distV2 += dist(1, v2);
distV2 += dist(v2, v1);
distV2 += dist(v1, N);

private static int dist(int start, int end) {
	//코드
}

 

3. Integer.MAX_VALUE로 초기화한 기존 dist배열을 문제 조건에 맞게. 간선 개수(200,000) * 거리(1000)의 최대 값인 2억개로 셋팅해준다.

private static int INF = 200000000;
Arrays.fill(dist, INF);

 

 

전체 코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    private static int N;
    private static int E;
    private static int v1;
    private static int v2;
    private static int[] dist;
    private static boolean[] visited;
    private static List<List<Node>> nodeList = new ArrayList<>();
    private static int INF = 200000000;

    static class Node implements Comparable<Node> {
        int node;
        int cost;

        public Node(int node, int cost) {
            this.node = node;
            this.cost = cost;
        }

        @Override
        public int compareTo(Node o) {
            return cost - o.cost;
        }
    }

    public static void main(String[] args) throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine(), " ");

        N = Integer.parseInt(st.nextToken());
        E = Integer.parseInt(st.nextToken());

        for(int i = 0; i <= N; i ++) {
            nodeList.add(new ArrayList<>());
        }

        for(int i = 0; i < E; i ++) {
            st = new StringTokenizer(br.readLine(), " ");
            int before = Integer.parseInt(st.nextToken());
            int after = Integer.parseInt(st.nextToken());
            int weight = Integer.parseInt(st.nextToken());

            nodeList.get(before).add(new Node(after, weight));
            nodeList.get(after).add(new Node(before, weight));
        }

        st = new StringTokenizer(br.readLine(), " ");
        v1 = Integer.parseInt(st.nextToken());
        v2 = Integer.parseInt(st.nextToken());

        dist = new int[N + 1];
        visited = new boolean[N + 1];

        int distV1 = 0;
        distV1 += dist(1, v1);
        distV1 += dist(v1, v2);
        distV1 += dist(v2, N);

        int distV2 = 0;
        distV2 += dist(1, v2);
        distV2 += dist(v2, v1);
        distV2 += dist(v1, N);

        System.out.println(distV1 >= INF && distV2 >= INF ? -1 : Math.min(distV1, distV2));
    }

    private static int dist(int start, int end) {
        Arrays.fill(dist, INF);
        Arrays.fill(visited, false);
        dist[start] = 0;

        PriorityQueue<Node> nodeQueue = new PriorityQueue<>();
        nodeQueue.offer(new Node(start, 0));

        while (!nodeQueue.isEmpty()) {
            Node curNode = nodeQueue.poll();
            int cur = curNode.node;

            if(!visited[cur]) {
                visited[cur] = true;

                for(Node node : nodeList.get(cur)) {
                    if(!visited[node.node] && dist[node.node] > dist[cur] + node.cost) {
                        dist[node.node] = dist[cur] + node.cost;
                        nodeQueue.offer(new Node(node.node, dist[node.node]));
                    }
                }
            }
        }
        return dist[end];
    }
}
728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[백준 1916번 / Java] 최소비용 구하기 / 다익스트라(Dijkstra)

P.S./백준 2023. 8. 13. 06:38
728x90
728x90

문제 링크

Gold 5

 

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

 

1916번: 최소비용 구하기

첫째 줄에 도시의 개수 N(1 ≤ N ≤ 1,000)이 주어지고 둘째 줄에는 버스의 개수 M(1 ≤ M ≤ 100,000)이 주어진다. 그리고 셋째 줄부터 M+2줄까지 다음과 같은 버스의 정보가 주어진다. 먼저 처음에는 그

www.acmicpc.net

 

 

 

 

 

첫 풀이

단순 BFS로 구해보려했던 것 같다.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    private static int N;
    private static int M;
    private static int[][] arr;
    private static int start;
    private static int end;
    private static List<Integer> answer = new ArrayList<>();

    public static void main(String[] args) throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        N = Integer.parseInt(br.readLine());
        M = Integer.parseInt(br.readLine());

        arr = new int[M][3];

        StringTokenizer st;

        for(int i = 0; i < M; i ++) {
            st = new StringTokenizer(br.readLine(), " ");

            for(int j = 0; j < 3; j ++) {
                arr[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        st = new StringTokenizer(br.readLine(), " ");
        start = Integer.parseInt(st.nextToken());
        end = Integer.parseInt(st.nextToken());

        Queue<int []> queue = new LinkedList<>();
        queue.add(new int[]{start, 0});

        bfs(queue);

        Collections.sort(answer);

        System.out.println(answer.get(0));

    }

    private static void bfs(Queue<int[]> queue) {
        while(!queue.isEmpty()) {
            int size = queue.size();

            for(int i = 0; i < size; i ++) {
                int[] tmp = queue.poll();
                int current = tmp[0];
                int cost = tmp[1];

                if(current == end) {
                    answer.add(cost);
                    continue;
                }

                for(int j = 0; j < M; j ++) {
                    if(arr[j][0] == current) {
                        queue.add(new int[] {arr[j][1], cost + arr[j][2]});
                    }
                }
            }
        }
    }
}

 

버스의 대수가 최대 10만대라는 조건이 있었으므로 당연히 시간초과가 발생했다.

 

 

 

두번째 풀이

최단거리 경로를 구할 때 활용하는 DP의 대표적인 최단 경로 탐색 알고리즘인 다익스트라(Dijkstra)를 활용했다.

 

다익스트라 알고리즘에 대한 자세한 설명은 아래 포스팅을 참고하기 바란다.

https://mag1c.tistory.com/452

 

bfs와의 차이는 아무래도, 현재 방문하고있는 노드에 대한 체크를 해주지 못해서 시간초과가 발생한 것 같다.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    private static int N;
    private static int M;
    private static int start;
    private static int end;
    private static int[] dist;
    private static boolean[] visited;
    private static List<List<Node>> nodeList = new ArrayList<>();

    static class Node implements Comparable<Node> {
        int node;
        int cost;

        public Node(int node, int cost) {
            this.node = node;
            this.cost = cost;
        }

        @Override
        public int compareTo(Node o) {
            return cost - o.cost;
        }
    }

    public static void main(String[] args) throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        N = Integer.parseInt(br.readLine());
        M = Integer.parseInt(br.readLine());
        StringTokenizer st;

        for(int i = 0; i <= N; i ++) {
            nodeList.add(new ArrayList<>());
        }

        for(int i = 0; i < M; i ++) {
            st = new StringTokenizer(br.readLine(), " ");
            int before = Integer.parseInt(st.nextToken());
            int after = Integer.parseInt(st.nextToken());
            int weight = Integer.parseInt(st.nextToken());

            nodeList.get(before).add(new Node(after, weight));
        }

        st = new StringTokenizer(br.readLine(), " ");
        start = Integer.parseInt(st.nextToken());
        end = Integer.parseInt(st.nextToken());

        dist = new int[N + 1];
        Arrays.fill(dist, Integer.MAX_VALUE);
        dist[start] = 0;
        visited = new boolean[N + 1];

        System.out.println(dist());
    }

    private static int dist() {
        PriorityQueue<Node> nodeQueue = new PriorityQueue<>();
        nodeQueue.offer(new Node(start, 0));

        while (!nodeQueue.isEmpty()) {
            Node curNode = nodeQueue.poll();
            int cur = curNode.node;

            if(!visited[cur]) {
                visited[cur] = true;

                for(Node node : nodeList.get(cur)) {
                    if(!visited[node.node] && dist[node.node] > dist[cur] + node.cost) {
                        dist[node.node] = dist[cur] + node.cost;
                        nodeQueue.offer(new Node(node.node, dist[node.node]));
                    }
                }
            }
        }

        return dist[end];
    }
}
728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

방명록