1. 문제

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

 

1738번: 골목길

첫째 줄에 골목길들이 교차하는 지점의 개수 n (2 ≤ n ≤ 100)과 골목길의 개수 m (1 ≤ m ≤ 20,000) 이 차례로 주어진다. 이어지는 m개의 행에 각각의 골목길을 나타내는 세 정수 u, v, w가 차례로

www.acmicpc.net

문제 설명

민승이는 놀러가기 위해 집을 나섰다. 민승이네 집에서 코레스코 콘도까지 가기 위해서는 복잡하게 얽혀있는 골목길들을 통과해야 한다.

그런데, 어떤 길에는 깡패가 서식하고 있어, 그 길을 지나게 되면 깡패에게 일정한 양의 금품을 갈취당하게 된다. 그런가하면, 어떤 길에는 지나가던 행인들이 흘리고 간 금품들이 떨어져 있어, 그 길을 지나게 되면 일정한 양의 금품을 획득하게 된다. 한 번 지나간 길을 다시 방문하더라도 금품을 갈취당하거나 획득한다.

골목길의 연결 상태와, 각 골목길을 지날 때 갈취당하거나 획득하게 되는 금품의 양이 주어졌을 때, 민승이가 최대한 유리한 경로를 따라 집에서 코레스코 콘도까지 가기 위해서는 어떻게 해야 하는지 출력하는 프로그램을 작성하시오. 

보유 중인 금품의 양이 음수가 될 수 있다. 최대한 유리한 경로 또는 최적의 경로는 민승이네 집에서 출발하여 코레스코 콘도에 도착하는 경로 중 금품의 양이 최대가 되는 경로이다. 

입력

첫째 줄에 골목길들이 교차하는 지점의 개수 n (2 ≤ n ≤ 100)과 골목길의 개수 m (1 ≤ m ≤ 20,000) 이 차례로 주어진다. 이어지는 m개의 행에 각각의 골목길을 나타내는 세 정수 u, v, w가 차례로 주어진다. 이는 u번 교차점에서 v번 교차점으로 이동할 수 있는 골목길이 나있다는 의미이다. 즉, 주어지는 골목길들은 기본적으로 모두 일방통행로이다. w (0 ≤ |w| ≤ 1,000)는 이 길을 지날 때 갈취당하거나 획득하게 되는 금품의 양이다. 음수는 갈취, 양수는 획득을 뜻한다.

골목길의 교차점 번호는 1이상 n이하의 정수이다. 민승이네 집은 1번 교차점에 있고, 이곳 코레스코 콘도는 n번 교차점에 있다.

출력

최적의 경로를 구할 수 있다면 민승이네 집부터 코레스코 콘도까지 가는 동안 거치게 되는 교차점들의 번호를 공백 하나를 사이에 두고 차례로 출력하면 된다. 그런데, 경우에 따라서는 최적의 경로라는 것이 존재하지 않는 상황이 발생한다. 어떠한 경우에 그런 상황이 발생하는지 생각해 보자. 그러한 경우에는 -1을 출력하도록 한다.

최적의 경로가 여러 개 존재할 때는 아무거나 출력해도 된다.

 

2. 어떻게 풀까

  • 최단 경로 알고리즘을 이용해서 해결 할 수 있다.
  • 이 문제의 경우 최솟값을 구하는게 아니라 최댓값을 구하는 것이므로 음수 사이클을 찾는 것이 아니라 양수 사이클을 찾아야 한다.
  • 사이클이 형성되는지 확인 해야하므로 벨만 포드 알고리즘을 이용하여 해결했다.
    • 사이클이 존재하더라도 사이클이 목적지에 영향을 주지 않는다면 상관없다.
      • 이 점을 간과해서 틀렸다. ㅜ

 

3. 코드 (Java)

package baekjoon.shortest_path.골목길_20220318;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main {

  private static final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));
  private static StringTokenizer tokenizer;

  private static int vertexCount;
  private static int edgeCount;
  private static int[][] edges;
  private static int[] distanceFromSource;
  private static int[] predecessor;

  public static void main(String[] args) throws IOException {
    tokenizer = new StringTokenizer(READER.readLine());
    vertexCount = Integer.parseInt(tokenizer.nextToken());
    edgeCount = Integer.parseInt(tokenizer.nextToken());

    init();
    bellmanFord();

    if (distanceFromSource[vertexCount] == Integer.MIN_VALUE) {
      System.out.println(-1);
      return;
    }

    for (int[] edge : edges) {
      int from = edge[0];
      int to = edge[1];
      int weight = edge[2];
      if (distanceFromSource[from] == Integer.MIN_VALUE) {
        continue;
      }

      if (isCycle(from, to, weight) && isCycleOnPath(to)) {
        System.out.println(-1);
        return;
      }
    }

    printShortestPath();
  }

  private static void init() throws IOException {
    edges = new int[edgeCount][];
    for (int i = 0; i < edgeCount; i++) {
      tokenizer = new StringTokenizer(READER.readLine());
      int from = Integer.parseInt(tokenizer.nextToken());
      int to = Integer.parseInt(tokenizer.nextToken());
      int weight = Integer.parseInt(tokenizer.nextToken());

      edges[i] = new int[]{from, to, weight};
    }

    distanceFromSource = new int[vertexCount + 1];
    predecessor = new int[vertexCount + 1];
    for (int i = 1; i <= vertexCount; i++) {
      distanceFromSource[i] = Integer.MIN_VALUE;
      predecessor[i] = -1;
    }
    distanceFromSource[1] = 0;
  }

  private static void bellmanFord() {
    for (int i = 0; i < vertexCount - 1; i++) {
      for (int[] edge : edges) {
        int from = edge[0];
        int to = edge[1];
        int weight = edge[2];
        if (distanceFromSource[from] == Integer.MIN_VALUE) {
          continue;
        }
        if (distanceFromSource[to] < distanceFromSource[from] + weight) {
          distanceFromSource[to] = distanceFromSource[from] + weight;
          predecessor[to] = from;
        }
      }
    }
  }

  private static boolean isCycle(int from, int to, int weight) {
    return distanceFromSource[to] < distanceFromSource[from] + weight;
  }

  private static boolean isCycleOnPath(int targetVertex) {
    boolean[] visit = new boolean[vertexCount + 1];
    Queue<Integer> queue = new LinkedList<>();
    queue.add(targetVertex);

    while (!queue.isEmpty()) {
      int currentVertex = queue.poll();
      if (visit[currentVertex]) {
        continue;
      }
      visit[currentVertex] = true;

      for (int[] edge : edges) {
        int from = edge[0];
        int to = edge[1];

        if (from == currentVertex && !visit[to]) {
          queue.add(to);
        }
      }
    }

    return visit[vertexCount];
  }

  private static void printShortestPath() {
    StringBuilder sb = new StringBuilder();
    int index = vertexCount;
    sb.append(vertexCount);
    while (predecessor[index] != -1) {
      sb.insert(0, predecessor[index] + " ");
      index = predecessor[index];
    }
    System.out.print(sb.toString());
  }
}
  • bellmanFord() 메소드를 통해 출발 노드부터 목적지 노드까지의 최단 경로를 구했다.
  • 목적지 노드까지의 경로가 없거나, 경로상에 양수 사이클이 존재할 경우 -1를 출력하도록 했다.
  • 경로 상에 양수 사이클이 존재하는지 검사했다.
    • 사이클이 존재하는 지 검증
      • 벨만 포드 알고리즘 이후에도 distanceFromSource가 갱신이 되는지 확인했다.
    • 사이클이 존재하고, 그 사이클이 경로상에 존재하는지 검증
      • 사이클이 존재하는 노드에서 목적지 노드까지의 경로가 있는지 BFS를 이용하여 확인했다.
      • 이 부분을 간과해서 틀렸다.
  • predecessor과 backtracking을 이용해서 최단 경로를 출력했다.
    • 처음에는 StringBuilder에 append()로 하나씩 추가한 후 reverse()로 마무리를 했었는데 계속 실패가 나와서 insert()로 바꾸었다.

 

벨만 포드 알고리즘을 알고 있다면 쉽게 풀 수 있긴 한데, 음수(문제는 양수)사이클이 출발 노드에서 목적지 노드까지의 경로에 포함되는지 검사하는 로직이 반드시 필요하다는것을 간과했다. 

'Coding Test > 백준' 카테고리의 다른 글

[백준] 1944. 복제 로봇 (Java)  (0) 2022.01.20
백준 - 1238 - 파티 (java)  (0) 2021.05.26
백준 - 16398 - 행성 연결 (java)  (0) 2021.05.25
백준 - 14502번 - 연구소 (java)  (0) 2021.05.25

1. 문제

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

 

1944번: 복제 로봇

첫째 줄에 미로의 크기 N(4 ≤ N ≤ 50)과 열쇠의 개수 M(1 ≤ M ≤ 250) 이 공백을 사이에 두고 주어진다. 그리고 둘째 줄부터 N+1째 줄까지 미로의 정보가 주어진다. 미로는 1과 0, 그리고 S와 K로 주어

www.acmicpc.net

문제 설명

세준이는 어느 날 획기적인 로봇을 한 개 개발하였다. 그 로봇은 복제 장치를 이용하면 자기 자신을 똑같은 로봇으로 원하는 개수만큼 복제시킬 수 있다. 세준이는 어느 날 이 로봇을 테스트하기 위하여 어떤 미로에 이 로봇을 풀어 놓았다. 이 로봇의 임무는 미로에 흩어진 열쇠들을 모두 찾는 것이다. 그리고 열쇠가 있는 곳들과 로봇이 출발하는 위치에 로봇이 복제할 수 있는 장치를 장착해 두었다.

N*N의 정사각형 미로와 M개의 흩어진 열쇠의 위치, 그리고 이 로봇의 시작 위치가 주어져 있을 때, 모든 열쇠를 찾으면서 로봇이 움직이는 횟수의 합을 최소로 하는 프로그램을 작성하여라. 로봇은 상하좌우 네 방향으로 움직이며, 로봇이 열쇠가 있는 위치에 도달했을 때 열쇠를 찾은 것으로 한다. (복제된 로봇이어도 상관없다) 하나의 칸에 동시에 여러 개의 로봇이 위치할 수 있으며, 로봇이 한 번 지나간 자리라도 다른 로봇 또는 자기 자신이 다시 지나갈 수 있다. 복제에는 시간이 들지 않으며, 로봇이 움직이는 횟수의 합은 분열된 로봇 각각이 움직인 횟수의 총 합을 말한다. 복제된 로봇이 열쇠를 모두 찾은 후 같은 위치로 모일 필요는 없다.

입력

첫째 줄에 미로의 크기 N(4 ≤ N ≤ 50)과 열쇠의 개수 M(1 ≤ M ≤ 250) 이 공백을 사이에 두고 주어진다. 그리고 둘째 줄부터 N+1째 줄까지 미로의 정보가 주어진다. 미로는 1과 0, 그리고 S와 K로 주어진다. 1은 미로의 벽을 의미하고, 0은 지나다닐 수 있는 길, S는 로봇이 출발하는 위치, K는 열쇠의 위치가 주어진다. S는 1개, K는 M개가 주어진다. S와 K에서만 복제를 할 수 있음에 유의한다. 미로는 벽으로 둘러쌓여 있는 형태이다. 즉, 모든 테두리는 벽이다.

출력

첫째 줄에 모든 로봇이 움직인 횟수의 총 합을 출력한다. 모든 열쇠를 찾는 것이 불가능한 경우 횟수 대신 첫째 줄에 -1을 출력하면 된다.

 

 

 

 

2. 어떻게 풀까

  • 각 Key와 Start 지점에서 로봇이 복제 될 수 있다는 것은 A지점에서 출발해서 B지점으로 가는 거리가 Start 지점에서 B지점으로 가는 거리가 더 가까울 수 있다는 뜻이다.
  • 그렇다는 것은..? 각 지점을 정점이라고 가정하고 각 정점끼리 간선이 이어져 있고 이 간선들을 이용해서 최소 신장 트리를 만들라는 얘기가 될 것이다.
  • 최소 신장 트리를 만들 수 없는 경우에는 -1을 출력하면 될 것이고, 최소 신장 트리가 만들어질 경우에는 그 가중치의 최솟값을 출력하면 되겠다.
  • 그렇다면 정점은 입력으로 주어진 map의 좌표를 이용해서 저장하면 될 것이고... 각 정점간의 간선을 어떻게 만들것인가.. map에서 상하좌우로만 움직일 수 있고, 각 좌표간 거리는 1이라고 했으니까 BFS를 이용해서 각 정점간의 거리를 구하고 그것을 이용해서 간선을 구현하면 되겠다.

드가보자

 

 

 

 

3. 코드 (자바, 통과한 코드)

package baekjoon.mst.복제로봇_20220120;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class Main {

  private static BufferedReader reader;
  private static int[] pointY = {0, 0, 1, -1};
  private static int[] pointX = {1, -1, 0, 0};
  private static List<int[]> edges;
  private static Vertex[] vertices;
  private static int[] parents;

  public static void main(String[] args) throws IOException {
    reader = new BufferedReader(new InputStreamReader(System.in));
    edges = new ArrayList<>();

    String[] parts = reader.readLine().split(" ");
    int mapSize = Integer.parseInt(parts[0]);
    int vertexCount = Integer.parseInt(parts[1]) + 1;
    vertices = new Vertex[vertexCount];
    char[][] maps = buildMapsAndAddVertex(mapSize);

    for (Vertex vertex : vertices) {
      getEdgesUsingBFS(maps, vertex);
    }

    parents = makeSet();
    int result = kruskal();

    System.out.println(isMST() ? result : -1);
  }

  private static char[][] buildMapsAndAddVertex(int mapSize) throws IOException {
    char[][] maps = new char[mapSize][mapSize];
    int vertexIndex = 0;

    for (int i = 0; i < mapSize; i++) {
      String line = reader.readLine();
      for (int j = 0; j < mapSize; j++) {
        maps[i][j] = line.charAt(j);
        if (isSourceOrKey(maps[i][j])) {
          vertices[vertexIndex] = new Vertex(i, j, vertexIndex++);
        }
      }
    }
    return maps;
  }

  private static boolean isSourceOrKey(char c) {
    return c == 'S' || c == 'K';
  }

  private static void getEdgesUsingBFS(char[][] maps, Vertex sourceVertex) {
    Queue<int[]> queue = new LinkedList<>();
    boolean[][] visit = new boolean[maps.length][maps.length];
    visit[sourceVertex.y][sourceVertex.x] = true;
    queue.add(new int[]{sourceVertex.y, sourceVertex.x, 0});

    while (!queue.isEmpty()) {
      int[] current = queue.poll();
      int currentY = current[0];
      int currentX = current[1];
      int distanceFromSourceVertex = current[2];

      for (int i = 0; i < 4; i++) {
        int nextY = currentY + pointY[i];
        int nextX = currentX + pointX[i];

        if (!visit[nextY][nextX] && isValidPoint(maps, nextY, nextX)) {
          visit[nextY][nextX] = true;
          queue.add(new int[]{nextY, nextX, distanceFromSourceVertex + 1});

          if (isSourceOrKey(maps[nextY][nextX])) {
            for (Vertex destinationVertex : vertices) {
              if (destinationVertex.y == nextY && destinationVertex.x == nextX) {
                edges.add(new int[]{sourceVertex.id, destinationVertex.id, distanceFromSourceVertex + 1});
                break;
              }
            }
          }
        }
      }
    }
  }

  private static boolean isValidPoint(char[][] maps, int nextY, int nextX) {
    return (nextY >= 0 && nextY < maps.length)
        && (nextX >= 0 && nextX < maps.length)
        && (maps[nextY][nextX] != '1');
  }

  private static int kruskal() {
    edges.sort(Comparator.comparingInt(x -> x[2]));
    int result = 0;
    for (int[] edge : edges) {
      int from = edge[0];
      int to = edge[1];
      int weight = edge[2];

      if (find(parents, from) != find(parents, to)) {
        union(parents, from, to);
        result += weight;
      }
    }
    return result;
  }

  private static int[] makeSet() {
    int[] parents = new int[vertices.length];
    for (int i = 0; i < parents.length; i++) {
      parents[i] = i;
    }
    return parents;
  }

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

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

    if (x == y) {
      return;
    }

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

  private static boolean isMST() {
    for (int i = 0; i < vertices.length; i++) {
      if (find(parents, i) != 0) {
        return false;
      }
    }
    return true;
  }

  private static class Vertex {

    int y;
    int x;
    int id;

    public Vertex(int y, int x, int id) {
      this.y = y;
      this.x = x;
      this.id = id;
    }
  }
}

부가 설명 없이 코드만 보고 이해할 수 있는 코드...라고 믿고 부가 설명은 달지 않는다. (이해가 안되신다면 댓글 감사하겠습니다..)

 

근데 틀렸었다.

무수한 틀렸습니다...

틀린 이유를 코드를 보면서 골똘히 생각해봤는데 두 부분에서 버그가 있었다.

          if (isSourceOrKey(maps[nextY][nextX])) {
            for (Vertex destinationVertex : vertices) {
              int destinationVertexId = -1;
              if (destinationVertex.y == nextY && destinationVertex.x == nextX) {
                destinationVertexId = destinationVertex.id;
                break;
              }
              edges.add(
                  new int[]{sourceVertex.id, destinationVertex.id, distanceFromSourceVertex + 1});
            }
          }

BFS를 이용해서 각 Vertex간의 거리를 구하고 탐색하는 좌표가 Vertex에 해당하면 Edge를 만들어 저장하는 부분이다. 여기서 edges.add() 부분이 if (destinationVertex.y == nextY && destinationVertex.x == nextX) 안에 있어야 했다. 그렇지 않으면 index가 -1인 상태로 edges에 추가되어서 버그가 발생할 수 있다.

 

    parents = makeSet();
    int result = kruskal();

    System.out.println(!edges.isEmpty() ? result : -1);

 

모든 Key에 닿을 수 있는 경우 가중치의 최솟값을 출력하고 그렇지 않은 경우 -1을 리턴하는 부분이다. 여기서 edges가 비어있는 경우를 모든 Key에 닿을 수 없는 경우로 생각하고 작성했는데, 생각해보니 edges 있어도 모든 Key에 닿을 수 있는 경우가 있을 수 있다. 그래서 find() 메소드로 모든 vertex들이 같은 집합인지 체크하는 isMST() 메소드를 작성하여 수정했다. 

'Coding Test > 백준' 카테고리의 다른 글

[백준] 1738. 골목길 (Java)  (0) 2022.03.21
백준 - 1238 - 파티 (java)  (0) 2021.05.26
백준 - 16398 - 행성 연결 (java)  (0) 2021.05.25
백준 - 14502번 - 연구소 (java)  (0) 2021.05.25

1. 문제

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

 

1238번: 파티

첫째 줄에 N(1 ≤ N ≤ 1,000), M(1 ≤ M ≤ 10,000), X가 공백으로 구분되어 입력된다. 두 번째 줄부터 M+1번째 줄까지 i번째 도로의 시작점, 끝점, 그리고 이 도로를 지나는데 필요한 소요시간 Ti가 들어

www.acmicpc.net

문제 설명

N개의 숫자로 구분된 각각의 마을에 한 명의 학생이 살고 있다.

어느 날 이 N명의 학생이 X (1 ≤ X ≤ N)번 마을에 모여서 파티를 벌이기로 했다. 이 마을 사이에는 총 M개의 단방향 도로들이 있고 i번째 길을 지나는데 Ti(1 ≤ Ti ≤ 100)의 시간을 소비한다.

각각의 학생들은 파티에 참석하기 위해 걸어가서 다시 그들의 마을로 돌아와야 한다. 하지만 이 학생들은 워낙 게을러서 최단 시간에 오고 가기를 원한다.

이 도로들은 단방향이기 때문에 아마 그들이 오고 가는 길이 다를지도 모른다. N명의 학생들 중 오고 가는데 가장 많은 시간을 소비하는 학생은 누구일지 구하여라.

제한 사항

입력

첫째 줄에 N(1 ≤ N ≤ 1,000), M(1 ≤ M ≤ 10,000), X가 공백으로 구분되어 입력된다. 두 번째 줄부터 M+1번째 줄까지 i번째 도로의 시작점, 끝점, 그리고 이 도로를 지나는데 필요한 소요시간 Ti가 들어온다. 시작점과 끝점이 같은 도로는 없으며, 시작점과 한 도시 A에서 다른 도시 B로 가는 도로의 개수는 최대 1개이다.

모든 학생들은 집에서 X에 갈수 있고, X에서 집으로 돌아올 수 있는 데이터만 입력으로 주어진다.

출력

첫 번째 줄에 N명의 학생들 중 오고 가는데 가장 오래 걸리는 학생의 소요시간을 출력한다.

입출력 예


2. 어떻게 풀까?

  • 처음 답
    • 주어진 입력으로 인접리스트를 만든다.
    • Dijkstra 알고리즘을 이용하여 각 지점에서 X지점까지의 최단거리를 구한다.
    • Dijkstra 알고리즘을 이용하여 X지점에서 각 지점까지의 최단거리를 구한다.
    • 가장 값이 큰 값을 출력한다.
  • 다른 답 참고
    • 인접리스트를 만들 때, from -> to의 정보를 to -> from 역방향 인접리스트를 하나 더 만든다.
    • 각 지점에서 X지점까지 최단거리는 역방향 인접리스트에서 보면 X지점에서 각 지점까지의 최단거리와 같다.
    • 그러므로
      • 각 지점에서 X지점까지 최단거리는 역방향 인접리스트를 이용하고
      • X지점에서 각 지점까지는 인접리스트를 이용한다.
    • 그 이후는 같다.

3. 코드

처음 답안 코드

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

 class Main {

  private static final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));
  private static final BufferedWriter WRITER = new BufferedWriter(new OutputStreamWriter(System.out));
  private static StringTokenizer tokenizer;
  private static int vertexCount;

  public static void main(String[] args) throws IOException {
    tokenizer = new StringTokenizer(READER.readLine());
    vertexCount = Integer.parseInt(tokenizer.nextToken());
    int edgeCount = Integer.parseInt(tokenizer.nextToken());
    int destination = Integer.parseInt(tokenizer.nextToken());

    List<Edge>[] adjList = new List[vertexCount + 1];
    for (int i = 1; i <= vertexCount; i++) {
      adjList[i] = new ArrayList<>();
    }
    for (int i = 0; i < edgeCount; i++) {
      tokenizer = new StringTokenizer(READER.readLine());
      int from = Integer.parseInt(tokenizer.nextToken());
      int to = Integer.parseInt(tokenizer.nextToken());
      int weight = Integer.parseInt(tokenizer.nextToken());

      adjList[from].add(new Edge(to, weight));
    }

    int max = Integer.MIN_VALUE;
    for (int i = 1; i <= vertexCount; i++) {
      int distance = dijkstra(adjList, i, destination) + dijkstra(adjList, destination, i);
      max = Math.max(max, distance);
    }

    WRITER.write(max + "");
    WRITER.flush();
    WRITER.close();
    READER.close();

  }

  private static int dijkstra(List<Edge>[] adjList, int source, int destination) {
    PriorityQueue<Edge> queue = new PriorityQueue<>(Comparator.comparingInt(o -> o.weight));
    int[] distance = new int[vertexCount + 1];
    Arrays.fill(distance, Integer.MAX_VALUE);
    boolean[] visit = new boolean[vertexCount + 1];
    queue.add(new Edge(source, 0));
    distance[source] = 0;

    while (!queue.isEmpty()) {
      Edge currentEdge = queue.poll();

      for (Edge adjEdge : adjList[currentEdge.to]) {
        if (distance[adjEdge.to] > distance[currentEdge.to] + adjEdge.weight) {
          distance[adjEdge.to] = distance[currentEdge.to] + adjEdge.weight;
          queue.add(new Edge(adjEdge.to, distance[adjEdge.to]));
        }
      }
    }

    return distance[destination];
  }
}

class Edge {

  int to;
  int weight;

  public Edge(int to, int weight) {
    this.to = to;
    this.weight = weight;
  }
}

다른 답 참고한 코드


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

 class Main {

  private static final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));
  private static final BufferedWriter WRITER = new BufferedWriter(new OutputStreamWriter(System.out));
  private static StringTokenizer tokenizer;
  private static int vertexCount;
  private static int edgeCount;

  public static void main(String[] args) throws IOException {
    tokenizer = new StringTokenizer(READER.readLine());
    vertexCount = Integer.parseInt(tokenizer.nextToken());
    edgeCount = Integer.parseInt(tokenizer.nextToken());
    int destination = Integer.parseInt(tokenizer.nextToken());

    List<Edge>[] adjList = new List[vertexCount + 1];
    List<Edge>[] reverseAdjList = new List[vertexCount + 1];

    buildAdjList(adjList, reverseAdjList);

    int max = Integer.MIN_VALUE;
    int[] go_distance = dijkstra(reverseAdjList, destination);
    int[] back_distance = dijkstra(adjList, destination);

    for (int i = 1; i <= vertexCount; i++) {
      max = Math.max(max, go_distance[i] + back_distance[i]);
    }

    WRITER.write(max + "");
    WRITER.flush();
    WRITER.close();
    READER.close();

  }

  private static void buildAdjList(List<Edge>[] adjList, List<Edge>[] reverseAdjList)
      throws IOException {
    for (int i = 1; i <= vertexCount; i++) {
      adjList[i] = new ArrayList<>();
      reverseAdjList[i] = new ArrayList<>();
    }
    for (int i = 0; i < edgeCount; i++) {
      tokenizer = new StringTokenizer(READER.readLine());
      int from = Integer.parseInt(tokenizer.nextToken());
      int to = Integer.parseInt(tokenizer.nextToken());
      int weight = Integer.parseInt(tokenizer.nextToken());

      adjList[from].add(new Edge(to, weight));
      reverseAdjList[to].add(new Edge(from, weight));
    }
  }

  private static int[] dijkstra(List<Edge>[] adjList, int source) {
    PriorityQueue<Edge> queue = new PriorityQueue<>(Comparator.comparingInt(o -> o.weight));
    int[] distance = new int[vertexCount + 1];
    Arrays.fill(distance, Integer.MAX_VALUE);
    queue.add(new Edge(source, 0));
    distance[source] = 0;

    while (!queue.isEmpty()) {
      Edge currentEdge = queue.poll();

      for (Edge adjEdge : adjList[currentEdge.to]) {
        if (distance[adjEdge.to] > distance[currentEdge.to] + adjEdge.weight) {
          distance[adjEdge.to] = distance[currentEdge.to] + adjEdge.weight;
          queue.add(new Edge(adjEdge.to, distance[adjEdge.to]));
        }
      }
    }

    return distance;
  }
}

class Edge {

  int to;
  int weight;

  public Edge(int to, int weight) {
    this.to = to;
    this.weight = weight;
  }
}

4. 느낀 점

  • 처음엔 Dijkstra 알고리즘만 알면 쉽게 풀 수 있는 문제라고 생각했는데 역방향 리스트를 만들어서 더 효율적으로 해결한 답을 보고 이런 방법도 있다는 것을 알 수 있었다. 알아 둬야겠다.
  • BellmanFord나 Floyd 알고리즘으로도 해결 할 수 있을듯

'Coding Test > 백준' 카테고리의 다른 글

[백준] 1738. 골목길 (Java)  (0) 2022.03.21
[백준] 1944. 복제 로봇 (Java)  (0) 2022.01.20
백준 - 16398 - 행성 연결 (java)  (0) 2021.05.25
백준 - 14502번 - 연구소 (java)  (0) 2021.05.25

1. 문제

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

 

16398번: 행성 연결

홍익 제국의 중심은 행성 T이다. 제국의 황제 윤석이는 행성 T에서 제국을 효과적으로 통치하기 위해서, N개의 행성 간에 플로우를 설치하려고 한다. 두 행성 간에 플로우를 설치하면 제국의 함

www.acmicpc.net

문제 설명

홍익 제국의 중심은 행성 T이다. 제국의 황제 윤석이는 행성 T에서 제국을 효과적으로 통치하기 위해서, N개의 행성 간에 플로우를 설치하려고 한다.

두 행성 간에 플로우를 설치하면 제국의 함선과 무역선들은 한 행성에서 다른 행성으로 무시할 수 있을 만큼 짧은 시간만에 이동할 수 있다. 하지만, 치안을 유지하기 위해서 플로우 내에 제국군을 주둔시켜야 한다.

모든 행성 간에 플로우를 설치하고 플로우 내에 제국군을 주둔하면, 제국의 제정이 악화되기 때문에 황제 윤석이는 제국의 모든 행성을 연결하면서 플로우 관리 비용을 최소한으로 하려 한다.

N개의 행성은 정수 1,…,N으로 표시하고, 행성 i와 행성 j사이의 플로우 관리비용은 Cij이며, i = j인 경우 항상 0이다.

제국의 참모인 당신은 제국의 황제 윤석이를 도와 제국 내 모든 행성을 연결하고, 그 유지비용을 최소화하자. 이때 플로우의 설치비용은 무시하기로 한다.

제한 사항

입력

입력으로 첫 줄에 행성의 수 N (1 ≤ N ≤ 1000)이 주어진다.

두 번째 줄부터 N+1줄까지 각 행성간의 플로우 관리 비용이 N x N 행렬 (Cij), (1 ≤ i, j ≤ N, 1 ≤ Cij ≤ 100,000,000, Cij = Cji) 로 주어진다.

출력

모든 행성을 연결했을 때, 최소 플로우의 관리비용을 출력한다.

입출력 예


2. 어떻게 풀까?

  • 최소 신장 트리이므로 Kruskal 알고리즘 혹은 Prim 알고리즘으로 해결한다.

3. 코드

Kruskal 알고리즘

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

 class Main {

  private static final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));
  private static final BufferedWriter WRITER = new BufferedWriter(new OutputStreamWriter(System.out));
  private static StringTokenizer tokenizer;
  private static int[] parents;

  public static void main(String[] args) throws IOException {
    tokenizer = new StringTokenizer(READER.readLine());
    int vertexCount = Integer.parseInt(tokenizer.nextToken());

    kruskal(vertexCount);

    WRITER.flush();
    WRITER.close();
    READER.close();
  }

  private static void kruskal(int vertexCount) throws IOException {
    List<int[]> edges = new ArrayList<>();
    for (int i = 1; i <= vertexCount; i++) {
      tokenizer = new StringTokenizer(READER.readLine());
      for (int j = 1; j <= vertexCount; j++) {
        int weight = Integer.parseInt(tokenizer.nextToken());
        if (weight != 0) {
          edges.add(new int[]{i, j, weight});
        }
      }
    }

    edges.sort(Comparator.comparingInt(o -> o[2]));

    parents = new int[vertexCount + 1];
    for (int i = 1; i <= vertexCount; i++) {
      parents[i] = i;
    }

    long sumWeight = 0;
    for (int i = 0; i < edges.size(); i++) {
      int[] current = edges.get(i);
      int from = current[0];
      int to = current[1];
      int weight = current[2];
      if (find(from) != find(to)) {
        union(from, to);
        sumWeight += weight;
      }
    }

    WRITER.write(sumWeight + "");
  }

  private static void union(int from, int to) {
    from = find(from);
    to = find(to);

    if (from > to) {
      parents[to] = from;
    } else {
      parents[from] = to;
    }
  }

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

Prim 알고리즘

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

 class Main {

  private static final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));
  private static final BufferedWriter WRITER = new BufferedWriter(new OutputStreamWriter(System.out));
  private static StringTokenizer tokenizer;

  public static void main(String[] args) throws IOException {
    tokenizer = new StringTokenizer(READER.readLine());
    int vertexCount = Integer.parseInt(tokenizer.nextToken());

    prim(vertexCount);

    WRITER.flush();
    WRITER.close();
    READER.close();
  }

  private static void prim(int vertexCount) throws IOException {
    int[][] adjMatrix = new int[vertexCount + 1][vertexCount + 1];
    for (int i = 1; i <= vertexCount; i++) {
      tokenizer = new StringTokenizer(READER.readLine());
      for (int j = 1; j <= vertexCount; j++) {
        adjMatrix[i][j] = Integer.parseInt(tokenizer.nextToken());
      }
    }

    PriorityQueue<int[]> queue = new PriorityQueue<>(Comparator.comparingInt(o -> o[1]));
    boolean[] visit = new boolean[vertexCount + 1];
    queue.add(new int[]{1, 0});

    long sumWeight = 0;
    while (!queue.isEmpty()) {
      int[] current = queue.poll();
      int to = current[0];
      int weight = current[1];

      if (visit[to]) {
        continue;
      }
      visit[to] = true;
      sumWeight += weight;

      for (int i = 1; i <= vertexCount; i++) {
        if (adjMatrix[to][i] != 0) {
          queue.add(new int[]{i, adjMatrix[to][i]});
        }
      }
    }

    WRITER.write(sumWeight + "");
  }
}

 


4. 느낀 점

  • 최소 신장 트리의 Prim, Kruskal 알고리즘을 안다면 쉽게 풀 수 있는 문제

'Coding Test > 백준' 카테고리의 다른 글

[백준] 1738. 골목길 (Java)  (0) 2022.03.21
[백준] 1944. 복제 로봇 (Java)  (0) 2022.01.20
백준 - 1238 - 파티 (java)  (0) 2021.05.26
백준 - 14502번 - 연구소 (java)  (0) 2021.05.25

1. 문제

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

 

2606번: 바이러스

첫째 줄에는 컴퓨터의 수가 주어진다. 컴퓨터의 수는 100 이하이고 각 컴퓨터에는 1번 부터 차례대로 번호가 매겨진다. 둘째 줄에는 네트워크 상에서 직접 연결되어 있는 컴퓨터 쌍의 수가 주어

www.acmicpc.net

문제 설명

인체에 치명적인 바이러스를 연구하던 연구소에서 바이러스가 유출되었다. 다행히 바이러스는 아직 퍼지지 않았고, 바이러스의 확산을 막기 위해서 연구소에 벽을 세우려고 한다.

연구소는 크기가 N×M인 직사각형으로 나타낼 수 있으며, 직사각형은 1×1 크기의 정사각형으로 나누어져 있다. 연구소는 빈 칸, 벽으로 이루어져 있으며, 벽은 칸 하나를 가득 차지한다. 

일부 칸은 바이러스가 존재하며, 이 바이러스는 상하좌우로 인접한 빈 칸으로 모두 퍼져나갈 수 있다. 새로 세울 수 있는 벽의 개수는 3개이며, 꼭 3개를 세워야 한다.

예를 들어, 아래와 같이 연구소가 생긴 경우를 살펴보자.

2 0 0 0 1 1 0
0 0 1 0 1 2 0
0 1 1 0 1 0 0
0 1 0 0 0 0 0
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

이때, 0은 빈 칸, 1은 벽, 2는 바이러스가 있는 곳이다. 아무런 벽을 세우지 않는다면, 바이러스는 모든 빈 칸으로 퍼져나갈 수 있다.

2행 1열, 1행 2열, 4행 6열에 벽을 세운다면 지도의 모양은 아래와 같아지게 된다.

2 1 0 0 1 1 0
1 0 1 0 1 2 0
0 1 1 0 1 0 0
0 1 0 0 0 1 0
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

바이러스가 퍼진 뒤의 모습은 아래와 같아진다.

2 1 0 0 1 1 2
1 0 1 0 1 2 2
0 1 1 0 1 2 2
0 1 0 0 0 1 2
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

벽을 3개 세운 뒤, 바이러스가 퍼질 수 없는 곳을 안전 영역이라고 한다. 위의 지도에서 안전 영역의 크기는 27이다.

연구소의 지도가 주어졌을 때 얻을 수 있는 안전 영역 크기의 최댓값을 구하는 프로그램을 작성하시오.

제한 사항

입력

첫째 줄에 지도의 세로 크기 N과 가로 크기 M이 주어진다. (3 ≤ N, M ≤ 8)

둘째 줄부터 N개의 줄에 지도의 모양이 주어진다. 0은 빈 칸, 1은 벽, 2는 바이러스가 있는 위치이다. 2의 개수는 2보다 크거나 같고, 10보다 작거나 같은 자연수이다.

빈 칸의 개수는 3개 이상이다.

출력

첫째 줄에 얻을 수 있는 안전 영역의 최대 크기를 출력한다.

입출력 예


2. 어떻게 풀까?

  • 벽을 먼저 3개 세운다.
  • 바이러스(2)를 중심으로 바이러스를 퍼뜨린다.
  • 0의 개수를 카운팅 한다.
  • 벽 세우는 것을 어떻게 할까?
    • DFS를 이용해서 벽을 세운다
  • 바이러스는 어떻게 퍼뜨릴까?
    • BFS나 BFS를 이용한다.
  • 벽이 3개 세워지는 모든 경우에서 0개수의 최대값을 찾아낸다.
  • 그리고 0개수를 출력!

3. 코드

백준문제는 입,출력을 사용자가 직접 해야해서 테스트코드를 따로 만들지 않았다.

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

class Main {

  private static final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));
  private static final BufferedWriter WRITER = new BufferedWriter(new OutputStreamWriter(System.out));
  private static StringTokenizer tokenizer;
  
  private static int maxSafeZone = Integer.MIN_VALUE;
  private static int row;
  private static int col;
  private static int[] yPoint = {1, -1, 0, 0};
  private static int[] xPoint = {0, 0, 1, -1};

  public static void main(String[] args) throws IOException {
    tokenizer = new StringTokenizer(READER.readLine());
    row = Integer.parseInt(tokenizer.nextToken());
    col = Integer.parseInt(tokenizer.nextToken());

    int[][] map = buildMap();
    installWall(map, 0);

    WRITER.write(maxSafeZone + "");
    WRITER.flush();
    WRITER.close();
    READER.close();
  }
  
  private static int[][] buildMap() throws IOException {
  	int[][] map = new int[row][col];
    for (int i = 0; i < row; i++) {
      tokenizer = new StringTokenizer(READER.readLine());
      for (int j = 0; j < col; j++) {
        map[i][j] = Integer.parseInt(tokenizer.nextToken());
      }
    }
    return map;
  }

  private static void installWall(int[][] map, int wallCount) {
    if (wallCount == 3) {
      int[][] virusMap = spreadVirus(map);
      int safeZone = countSafeZone(virusMap);
      maxSafeZone = Math.max(safeZone, maxSafeZone);
      return;
    }

    for (int i = 0; i < row; i++) {
      for (int j = 0; j < col; j++) {
        if (map[i][j] == 0) {
          map[i][j] = 1;
          installWall(map, wallCount + 1);
          map[i][j] = 0;
        }
      }
    }
  }

  private static int countSafeZone(int[][] map) {
    int count = 0;
    for (int i = 0; i < row; i++) {
      for (int j = 0; j < col; j++) {
        if (map[i][j] == 0) {
          count++;
        }
      }
    }
    return count;
  }

  private static int[][] spreadVirus(int[][] map) {
    int[][] copyMap = new int[row][col];
    for (int i = 0; i < row; i++) {
      for (int j = 0; j < col; j++) {
        copyMap[i][j] = map[i][j];
      }
    }

    boolean[][] visit = new boolean[row][col];

    for (int i = 0; i < row; i++) {
      for (int j = 0; j < col; j++) {
        if (copyMap[i][j] == 2 && !visit[i][j]) {
          bfs(copyMap, visit, i, j);
        }
      }
    }
    return copyMap;
  }

  private static void bfs(int[][] map, boolean[][] visit, int startY, int startX) {
    Queue<int[]> queue = new LinkedList<>();
    queue.add(new int[]{startY, startX});
    visit[startY][startX] = true;

    while (!queue.isEmpty()) {
      int[] current = queue.poll();

      for (int i = 0; i < 4; i++) {
        int y = current[0] + yPoint[i];
        int x = current[1] + xPoint[i];

        if (y < 0 || y >= row) {
          continue;
        }
        if (x < 0 || x >= col) {
          continue;
        }
        if (map[y][x] == 0 && !visit[y][x]) {
          visit[y][x] = true;
          map[y][x] = 2;
          queue.add(new int[]{y, x});
        }
      }
    }
  }

}
  • buildMap() : 입력으로 받은 정보로 연구소 지도를 만든다.
  • installWall(int[][] map, int wallCount) : DFS를 이용하여 0을 1로 바꿔 벽을 세운다. 벽이 3개이면 바이러스를 퍼뜨리고 안전한 장소(0)을 카운팅한다.
  • countSafeZone(int[][] map) : 0 개수를 카운팅한다.
  • spreadVirus(int[][] map) : 연구소 지도를 돌며 2인 지점을 찾아내고 2에서 bfs()를 통해 바이러스를 퍼뜨린다.
  • bfs(int[][] map, boolean[][] visit, int startY, int startX) : 2인 지점을 시작으로 바이러스를 퍼뜨린다 ( 0 -> 2)

4. 느낀 점

  • 벽을 설치하는 과정이 Brute-Force라서 시간초과 되지 않을까? 라는 생각에 다른 방법을 생각하다가 더 생각이 안나서 그냥 Brute-Force 방식을 사용했다.
  • 그 외에는 딱히 어려운 것 없는 문제

'Coding Test > 백준' 카테고리의 다른 글

[백준] 1738. 골목길 (Java)  (0) 2022.03.21
[백준] 1944. 복제 로봇 (Java)  (0) 2022.01.20
백준 - 1238 - 파티 (java)  (0) 2021.05.26
백준 - 16398 - 행성 연결 (java)  (0) 2021.05.25

+ Recent posts