백준 17472 다리 만들기2

2022. 5. 28. 20:30·Nodejs로 알고리즘 박살내기
 

17472번: 다리 만들기 2

첫째 줄에 지도의 세로 크기 N과 가로 크기 M이 주어진다. 둘째 줄부터 N개의 줄에 지도의 정보가 주어진다. 각 줄은 M개의 수로 이루어져 있으며, 수는 0 또는 1이다. 0은 바다, 1은 땅을 의미한다.

www.acmicpc.net

문제 핵심 파악하기.

1. 모든 섬을 최소 길이의 다리로 모두 이어줄 때 다리 길이의 합을 출력하는 문제입니다.

2. 섬과 섬을 다리로 이어줄 때 길이는 2이상이어야 하며,  다리가 교차하더라도 교차 구간의 길이가 1이 되는 것이 아닌 각각의 다리의 길이를 포함하여야 합니다.

 

풀이.

1. 각각의 섬을 다음과 번호를 붙여 구분해 줄 것입니다.

섬 넘버링 해주기.

2. 각각의 섬이 다른 섬으로 이동할 때 최소 거리를 완전 탐색 + bfs를 이용하여 구해줍니다.

 

3. 이제 간단한 vertex와 edge를 가지는 그래프 형식으로 생각해 주겠습니다.

 1번 섬에서 2번 섬으로 갈 때 최소비용 2 간단히 하여[ 1 -> 2 [2], 2 -> 3 [2], 3 -> 5 [2], 3 -> 4 [3] ]

(1번 노드에서 2번 노드로 가중치 2 비용)이라 생각하고 edges를 하나의 배열에 모아줍니다.

 

4. union-find 알고리즘을 통해 모든 노드가 이어질 때 최소 비용을 구해줍니다.

 

const readline = require("readline");
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

let N, M;
let map = [];
let distance = [];
const INF = Infinity;
const dx = [0, 1, 0, -1];
const dy = [1, 0, -1, 0];

// 최소 스패닝 비용을 구하기 위해 union-find를 만들어 주었습니다.
function UnionFind(N) {
  this.parents = Array.from({ length: N + 1 }, (_, i) => i);

  this.getParent = (num) => {
    if (this.parents[num] === num) return num;
    return (this.parents[num] = this.getParent(this.parents[num]));
  };

  this.unionParents = (a, b) => {
    const aParent = this.getParent(a);
    const bParent = this.getParent(b);

    if (aParent > bParent) this.parents[aParent] = bParent;
    else this.parents[bParent] = aParent;
  };

  this.findParent = (a, b) => {
    const aParent = this.getParent(a);
    const bParent = this.getParent(b);

    return aParent === bParent;
  };
}

// 각각의 섬을 구분하기 위해 넘버링하는 함수 입니다.
function isLandNumbering(i, j, isLandNum) {
  const queue = [[i, j]];

  let index = 0;
  while (queue.length > index) {
    const [y, x] = queue[index++];
    map[y][x] = isLandNum;

    for (let i = 0; i < 4; i++) {
      const nx = x + dx[i];
      const ny = y + dy[i];

      if (nx < 0 || ny < 0 || nx >= M || ny >= N) continue;
      if (map[ny][nx] === -1) {
        queue.push([ny, nx]);
      }
    }
  }
}

// 섬에서 다른 섬으로 이동할 때 최소 비용을 구하는 함수입니다.
function searchMinDist(queue, now) {
  let index = 0;
  while (queue.length > index) {
    const [y, x, dist, dir] = queue[index++];

    const stack = [];
    if (dir === -1) {
      for (let i = 0; i < 4; i++) {
        const nx = x + dx[i];
        const ny = y + dy[i];

        stack.push([ny, nx, i]);
      }
    } else {
      const nx = x + dx[dir];
      const ny = y + dy[dir];

      stack.push([ny, nx, dir]);
    }

    while (stack.length) {
      const [ny, nx, nDir] = stack.pop();

      if (nx < 0 || ny < 0 || nx >= M || ny >= N) continue;
      const next = map[ny][nx];
      if (next === now) continue;
      if (next !== 0 && dist > 1) {
        distance[now][next] = Math.min(distance[now][next], dist);
      }
      if (next === 0) {
        queue.push([ny, nx, dist + 1, nDir]);
      }
    }
  }
}

rl.on("line", function (line) {
  if (!N) {
    [N, M] = line.split(" ").map(Number);
    visit = Array.from({ length: N }, () => Array(M).fill(false));
  } else {
    map.push(line.split(" ").map((n) => (n === "1" ? -1 : +n)));

    if (map.length === N) {
      rl.close();
    }
  }
}).on("close", function () {
  // 섬 구역별로 번호 매기기
  let isLandNumber = 0;
  for (let i = 0; i < N; i++) {
    for (let j = 0; j < M; j++) {
      if (map[i][j] === -1) {
        isLandNumber += 1;
        isLandNumbering(i, j, isLandNumber);
      }
    }
  }

  distance = Array.from({ length: isLandNumber + 1 }, () =>
    Array(isLandNumber + 1).fill(INF)
  );

  // 섬에서 섬으로 갈 수 있는 최소 거리 구하기
  // 완전 탐색하여 각 섬에서 각 섬으로 탐색해야 합니다.
  for (let k = 1; k <= isLandNumber; k++) {
    const queue = [];
    for (let i = 0; i < N; i++) {
      for (let j = 0; j < M; j++) {
        if (map[i][j] === k) {
          queue.push([i, j, 0, -1]);
        }
      }
    }

    searchMinDist(queue, k);
  }

  const edges = [];
  for (let i = 1; i <= isLandNumber; i++) {
    for (let j = 1; j <= isLandNumber; j++) {
      const dist = distance[i][j];
      if (dist !== INF) {
        edges.push([i, j, dist]);
      }
    }
  }

  const uf = new UnionFind(isLandNumber);
  let result = 0;
  edges.sort((a, b) => a[2] - b[2]);
  for (let i = 0; i < edges.length; i++) {
    const [from, to, dist] = edges[i];
    if (!uf.findParent(from, to)) {
      uf.unionParents(from, to);
      result += dist;
    }
  }

  // 갈 수 없는 섬이 있는지 확인
  let flag = false;
  for (let i = 2; i <= isLandNumber; i++) {
    if (!uf.findParent(1, i)) {
      flag = true;
      break;
    }
  }
  
  // flag === true(갈 수 없는 섬이 있다)라면 -1을 출력
  // 아니면 최소 비용 출력
  console.log(flag ? -1 : result);

  process.exit();
});
저작자표시 비영리 변경금지 (새창열림)

'Nodejs로 알고리즘 박살내기' 카테고리의 다른 글

백준 17090 미로 탈출하기  (0) 2022.05.27
백준 2668 숫자고르기  (0) 2022.05.27
백준 5719 거의 최단 경로  (0) 2022.05.22
백준 2212 센서 js  (0) 2022.05.18
백준 1461 도서관 js  (0) 2022.05.17
'Nodejs로 알고리즘 박살내기' 카테고리의 다른 글
  • 백준 17090 미로 탈출하기
  • 백준 2668 숫자고르기
  • 백준 5719 거의 최단 경로
  • 백준 2212 센서 js
heesu
heesu
개발하면서 겪었던 경험을 공유합니다.
  • heesu
    heesu dev
    heesu
  • 전체
    오늘
    어제
    • 분류 전체보기
      • Nodejs로 알고리즘 박살내기
      • 사이드 프로젝트
        • Bitfolio
      • React Native
      • Etc
      • HTML
      • NextJS
      • Javascript
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Nodejs
    크루스칼 알고리즘
    redux-persist
    완전 탐색
    백준
    letsencrypt
    프로그래머스
    JavaScript
    React Native
    certbot
    정렬
    투 포인터
    lv4
    react-native
    Dijkstra
    크루스칼
    BFS
    asyncstorage
    벨만포드 알고리즘
    dfs
    시맨틱 태그
    최소 스패닝
    union-find
    Animated
    우선순위 큐
    학습
    cleancode
    UnionFind
    알고리즘
    부분합
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
heesu
백준 17472 다리 만들기2
상단으로

티스토리툴바