danbibibi
article thumbnail

문제

문제 바로가기> BOJ 17472번: 다리 만들기 2

 

17472번: 다리 만들기 2

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

www.acmicpc.net

 

풀이

1. 섬 (2 ≤ 섬의 개수 ≤ 6) 대표 번호? 를 매겨주었다.

   어떤 섬에서 어떤 섬으로 이어지는 다리인지 알기 위해서!

 

2. 문제에서 준 조건인,

    ▷ 다리의 길이 > 2

     중간에 방향 변화 불가

를 만족 시키는 다리(edge)가 이어주는 섬 두개 (from, to), 다리 길이(len)를 pq에 넣어준다! 

이 때, 상 하 좌 우 탐색을 모두 진행하면, 중복되는 edge들이 발생하므로,
    상하 중 하나,
    좌우 중 하나
를 골라서 총 2가지 방향에 대한 탐색만 진행하면 된다!
4가지 다 해도 틀리진 않지만, 굳이 중복되는 걸 할 필요는 없으니까?

 

3. 후보 다리들을 이용하여, MST를 만들 수 있다면, 모든 섬을 연결할 수 있고,

    MST를 생성하는 데 사용된 다리 길이모든 섬을 연결하는 다리 길이의 최솟값이 된다!!

 

C++

#include<iostream>
#include<queue>
#define MAXISLAND 8
#define MAX 11
#define SEA 0
#define LAND 1
using namespace std;

struct EDGE { 
	int from, to, len;
	bool operator < (const EDGE &e) const {
		if (len == e.len) {
			if (from == e.from) return to > e.to;
			return from > e.from;
		} return len > e.len;
	}
}; priority_queue<EDGE> pq; // 다리 후보들

int N, M;
int map[MAX][MAX];
int root[MAXISLAND];
int dy[] = { -1, 0, 1, 0 }; // 상 좌 하 우
int dx[] = { 0, -1, 0, 1 };

void input() {
	cin >> N >> M;
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++) cin >> map[i][j];
	}
}

int find_(int x) {
	if (x == root[x]) return x;
	return root[x] = find_(root[x]);
}

void union_(int x, int y) {
	root[find_(x)] = find_(y);
}

void grouping(int y, int x, int num) {
	if (y < 0 || y >= N || x < 0 || x >= M) return; // 범위를 벗어나는 경우
	if (map[y][x] != LAND) return; // 땅이 아닌 경우
	map[y][x] = num;
	for (int d = 0; d < 4; d++) grouping(y + dy[d], x + dx[d], num);
}

void make_candidates() {
	for (int y = 0; y < N; y++) {
		for (int x = 0; x < M; x++) {
			
			if (map[y][x] == SEA) continue; // 바다인 경우
			
			int from = map[y][x]; // 출발지
			int to, len;
			for (int d = 0; d < 2; d++) { // 상,좌 만 탐색 (겹치는 다리를 없도록 하기 위해)
				to = 0; len = 0; // 도착지, 다리 길이

				int ny = y, nx = x;
				while (1) {
					ny+=dy[d]; nx+=dx[d]; // move
					len++; // 이동 거리
					if (ny < 0 || ny >= N || nx < 0 || nx >= M ) break; // 범위를 벗어나는 경우
					if (map[ny][nx] == from) break; // 같은 섬인 경우
					if (map[ny][nx] > LAND) { // 다른 섬에 도착한 경우
						if(--len > 1) to = map[ny][nx]; // 다리의 길이는 2 이상
						break;
					}
				} if (to != 0) pq.push({from, to, len});
			}
		}
	}
}

int make_bridge(int need) { // need : MST edge 수 
	for (int i = 2; i < MAXISLAND; i++) root[i] = i; // union-find를 위한 초기화
	int ans = 0, bridge_num = 0;
	while (!pq.empty() && bridge_num != need) {
		int len = pq.top().len;
		int from = pq.top().from;
		int to = pq.top().to;
		pq.pop();

		if (find_(from) != find_(to)) { // 연결되지 않은 경우
			ans += len;
			bridge_num++;
			union_(from, to); // 연결
		}
	}
	if (bridge_num != need) return -1; // 모든 섬을 연결하는 것이 불가능한 경우
	return ans;
}

void solution() { // 모든 섬을 연결하는 것이 불가능하면 -1을 출력
	
    // 1. grouping
	int island_num = 1;
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++) {
			if (map[i][j] == LAND) grouping(i, j, ++island_num); // 각 섬에 대표 번호를 매김 (start with 2)
		}
	}

	// 2. 다리 후보 선정
	make_candidates();

	// 3. MST로 다리 만들기
	cout << make_bridge(island_num-2);
}

int main() {
	ios_base::sync_with_stdio(0); cin.tie(0); 
	input();
	solution(); //모든 섬을 연결하는 다리 길이의 최솟값
}

 

Java

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

public class Main {
	
	static class Bridge implements Comparable<Bridge>{
		int from, to, len;
		public Bridge(int from, int to, int len) {
			this.from = from;
			this.to = to;
			this.len = len;
		}

		@Override
		public int compareTo(Bridge o) {
			if(this.len == o.len ) {
				if(this.to == o.to) return this.to - o.to;
				return this.from - o.from;
			} return this.len - o.len;
		}
		
	}
	
	static PriorityQueue<Bridge> pq = new PriorityQueue<>();
	static final int dy[] = { -1, 0, 1, 0 };
	static final int dx[] = { 0, -1, 0, 1 };
	static final int MAXISLAND = 8;
	static final int SEA = 0;
	static final int LAND = 1;
	
	static int N, M;
	static int[][] map;
	static int[] root = new int[MAXISLAND];
	
	static void solution() {
		
	    // 1. grouping
		int island_num = 1;
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < M; j++) {
				if (map[i][j] == LAND) grouping(i, j, ++island_num); // 각 섬에 대표 번호를 매김 (start with 2)
			}
		}

		// 2. 다리 후보 선정
		make_candidates();

		// 3. MST로 다리 만들기
		System.out.println(make_bridge(island_num-2));
		
	}

	static void grouping(int y, int x, int num) {
		if (y < 0 || y >= N || x < 0 || x >= M) return; // 범위를 벗어나는 경우
		if (map[y][x] != LAND) return; // 땅이 아닌 경우
		map[y][x] = num;
		for (int d = 0; d < 4; d++) grouping(y + dy[d], x + dx[d], num);
	}
	
	static void make_candidates() {
		for (int y = 0; y < N; y++) {
			for (int x = 0; x < M; x++) {
				
				if (map[y][x] == SEA) continue; // 바다인 경우
				
				int from = map[y][x]; // 출발지
				int to, len;
				for (int d = 0; d < 2; d++) { // 상,좌 만 탐색 (겹치는 다리를 없도록 하기 위해)
					to = 0; len = 0; // 도착지, 다리 길이

					int ny = y, nx = x;
					while (true) {
						ny+=dy[d]; nx+=dx[d]; // move
						len++; // 이동 거리
						if (ny < 0 || ny >= N || nx < 0 || nx >= M ) break; // 범위를 벗어나는 경우
						if (map[ny][nx] == from) break; // 같은 섬인 경우
						if (map[ny][nx] > LAND) { // 다른 섬에 도착한 경우
							if(--len > 1) to = map[ny][nx]; // 다리의 길이는 2 이상
							break;
						}
					} 
					if (to != 0) pq.add(new Bridge(from, to, len));
				}
			}
		}
	}
	
	static int find_(int x) {
		if (x == root[x]) return x;
		return root[x] = find_(root[x]);
	}
	
	static void union_(int x, int y) {
		root[find_(x)] = find_(y);
	}
	
	static int make_bridge(int need) {
		for (int i = 2; i < MAXISLAND; i++) root[i] = i; // union-find를 위한 초기화
		int ans = 0, bridge_num = 0;
		while (!pq.isEmpty() && bridge_num != need) {
			int len = pq.peek().len;
			int from = pq.peek().from;
			int to = pq.peek().to;
			pq.poll();

			if (find_(from) != find_(to)) { // 연결되지 않은 경우
				ans += len;
				bridge_num++;
				union_(from, to); // 연결
			}
		}
		if (bridge_num != need) return -1; // 모든 섬을 연결하는 것이 불가능한 경우
		return ans;
	}
	
	
	public static void main(String[] args) throws Exception {
		// System.setIn(new FileInputStream("res/input.txt"));
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		N = Integer.parseInt(st.nextToken());
		M = Integer.parseInt(st.nextToken());
		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());
		}
		
		solution(); //모든 섬을 연결하는 다리 길이의 최솟값
	}
}

'문제 풀이 > 백준' 카테고리의 다른 글

BOJ 9663번: N-Queen  (0) 2023.02.16
BOJ 2023번: 신기한 소수  (0) 2023.02.14
BOJ 17478번: 재귀함수가 뭔가요?  (0) 2023.02.06
BOJ 17135번: 캐슬 디펜스  (0) 2023.01.27
BOJ 17070번: 파이프 옮기기1  (1) 2023.01.25
profile

danbibibi

@danbibibi

꿈을 꾸는 시간은 멈춰 있는 것이 아냐 두려워하지 마 멈추지 마 푸른 꿈속으로