https://www.acmicpc.net/problem/17472
N*M 크기의 이차원 격자에 땅 or 바다간만에 구현 문제를 풀어보았습니다..
(오랜만에 하니 빡세더군요..)
그래도 구현해야 할 부분들을 나눠서 구현하면 생각보단(?) 금방 풀 수 있는 문제입니다.
문제 해석부터 해보겠습니다.
N*M 사이즈의 격자이 정도만 고려하면 될 것 같습니다.
우선 입력부터 받아봅시다.
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());
m = Integer.parseInt(st.nextToken());
map = new int[n][m];
visited = new boolean[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());
}
}
map으로 선언했습니다. private static void grouping() {
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (map[i][j] == 1) {
bfs(i, j);
label++;
}
}
}
}
1)을 만나면 BFS를 통해 인접한 땅들을 하나의 그룹으로 만들어 줍니다. private static void bfs(int sr, int sc) {
Queue<int[]> queue = new ArrayDeque<>();
queue.add(new int[] { sr, sc });
visited[sr][sc] = true; // 방문 처리
map[sr][sc] = label; // 시작 좌표부터 label로 채움
while (!queue.isEmpty()) {
int[] cur = queue.poll();
int r = cur[0], c = cur[1];
// 4방 탐색
for (int i = 0; i < 4; i++) {
int nr = r + dr[i], nc = c + dc[i];
if (nr < 0 || nr >= n || nc < 0 || nc >= m) continue; // 범위 밖이면 pass
if (map[nr][nc] == 0 || visited[nr][nc]) continue; // 땅이 아니거나, 이미 방문한 좌표라면 pass
map[nr][nc] = label; // 현재 땅을 label로 채움
visited[nr][nc] = true; // 방문 처리
queue.add(new int[] { nr, nc });
}
}
}
grouping() 메서드에서 가져온 좌표를 기준으로 인접한 땅들을 모두 label에 주어진 번호로 채워 줍니다.label은 2로 선언해 주었습니다.label을 증가 시켜주었습니다.(grouping() 메서드 참조)그룹화가 끝났으면, 다리를 지어주도록 합시다.
private static void buildEdges() {
// 다리의 최소 길이를 저장할 배열
int[][] lens = new int[label][label];
for (int i = 2; i < label; i++) Arrays.fill(lens[i], INF);
for (int r = 0; r < n; r++) {
for (int c = 0; c < m; c++) {
int start = map[r][c]; // 현재 좌표
if (start <= 1) continue; // 하나의 나라가 아니라면 pass
// 4방 탐색
for (int i = 0; i < 4; i++) {
int len = 0; // 길이를 저장할 변수
int nr = r + dr[i], nc = c + dc[i];
// 범위 내일 경우에 계속 반복
while (0 <= nr && nr < n && 0 <= nc && nc < m) {
int bridge = map[nr][nc]; // 다리를 시작할 좌표
if (bridge == start) break; // 다리의 시작 좌표가 땅이라면 break
// 다리의 시작이 바다라면
if (bridge == 0) {
len++; // 길이 증가
// 다음 좌표로 뻗어나감
nr += dr[i];
nc += dc[i];
continue;
}
// 다리 길이가 2 이상이고 다른 나라를 만났다면
if (len >= 2) {
int end = bridge; // 현재 좌표를 다리의 끝으로 설정
// 만약 start -> end 까지의 길이가 최솟값이라면 갱신
if (lens[start][end] > len) {
lens[start][end] = len;
lens[end][start] = len;
}
}
break;
}
}
}
}
edges = new ArrayList<>();
for (int u = 2; u < label; u++) {
for (int v = u + 1; v < label; v++) {
if (lens[u][v] != INF) edges.add(new Edge(u, v, lens[u][v]));
}
}
}
edges 배열에 최댓값이 아닌 경우의 값들을 모두 넣어 줍니다.class Edge implements Comparable<Edge> {
int u, v, w;
Edge (int u, int v, int w) {
this.u = u;
this.v = v;
this.w = w;
}
@Override
public int compareTo(Edge o) {
return Integer.compare(this.w, o.w);
}
}
모든 섬들 사이에 다리들을 놓아준 최소 다리 길이 배열을 얻었으니, 이를 이용해서 모든 섬들을 잇는 다리들을 배치해 주겠습니다.
다리를 놓았을 때 모든 다리가 연결되는지 확인하기 위해 유니온 파인드 알고리즘을 사용하였습니다.
private static void make() {
p = new int[label];
s = new int[label];
for (int i = 2; i < label; i++) {
p[i] = i; // 자기 자신의 부모는 자신
s[i] = 1; // 자신 혼자 존재하니 크기는 1
}
}
private static int find(int x) {
if (p[x] == x) return x; // 부모가 자신이면 자신 리턴
return p[x] = find(p[x]); // 경로 압축
}
find() 메서드 private static boolean union(int a, int b) {
int ra = find(a), rb = find(b); // a와 b의 부모
if (ra == rb) return false; // 부모가 같다면 return false
// ra를 기준으로 값을 넣을 것이기 때문에
// rb의 크기가 더 크다면 서로 위치 swap
if (s[ra] < s[rb]) {
int t = ra;
ra = rb;
rb = t;
}
// rb의 부모를 ra로 설정
p[rb] = ra;
s[ra] += s[rb]; // ra 밑에 들어가는 것이기 때문에 rb의 크기 추가
return true; // 유니온 연산이 진행되었으니 return true
}
union() 메서드 private static int kruskal() {
Collections.sort(edges); // 가중치 기준 정렬
int mstCost = 0; // 최소 다리 길이
int usedEdges = 0; // 사용한 다리 개수
for (Edge e : edges) {
if (union(e.u, e.v)) { // union 연산이 이루어졌다면,
mstCost += e.w; // 다리길이 추가
if (++usedEdges == label - 3) { // 모든 다리의 배치가 끝났다면
return mstCost; // 최소 다리 길이 리턴
}
}
}
return -1; // 모든 섬을 연결하지 못했다면 -1 리턴
}
kruskal() 메서드label - 3임을 헷갈리시면 안 됩니다!!import java.util.*;
import java.io.*;
public class Main {
static int n, m;
static int[][] map;
static boolean[][] visited;
static int[] p, s;
static List<Edge> edges;
static int label = 2;
static final int[] dr = { -1, 1, 0, 0 };
static final int[] dc = { 0, 0, -1, 1 };
static final int INF = 1_000_000_000;
private static void grouping() {
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (map[i][j] == 1) {
bfs(i, j);
label++;
}
}
}
}
private static void bfs(int sr, int sc) {
Queue<int[]> queue = new ArrayDeque<>();
queue.add(new int[] { sr, sc });
visited[sr][sc] = true;
map[sr][sc] = label;
while (!queue.isEmpty()) {
int[] cur = queue.poll();
int r = cur[0], c = cur[1];
for (int i = 0; i < 4; i++) {
int nr = r + dr[i], nc = c + dc[i];
if (nr < 0 || nr >= n || nc < 0 || nc >= m) continue;
if (map[nr][nc] == 0 || visited[nr][nc]) continue;
map[nr][nc] = label;
visited[nr][nc] = true;
queue.add(new int[] { nr, nc });
}
}
}
private static void make() {
p = new int[label];
s = new int[label];
for (int i = 2; i < label; i++) {
p[i] = i;
s[i] = 1;
}
}
private static void buildEdges() {
int[][] lens = new int[label][label];
for (int i = 2; i < label; i++) Arrays.fill(lens[i], INF);
for (int r = 0; r < n; r++) {
for (int c = 0; c < m; c++) {
int start = map[r][c];
if (start <= 1) continue;
for (int i = 0; i < 4; i++) {
int len = 0;
int nr = r + dr[i], nc = c + dc[i];
while (0 <= nr && nr < n && 0 <= nc && nc < m) {
int bridge = map[nr][nc];
if (bridge == start) break;
if (bridge == 0) {
len++;
nr += dr[i];
nc += dc[i];
continue;
}
if (len >= 2) {
int end = bridge;
if (lens[start][end] > len) {
lens[start][end] = len;
lens[end][start] = len;
}
}
break;
}
}
}
}
edges = new ArrayList<>();
for (int u = 2; u < label; u++) {
for (int v = u + 1; v < label; v++) {
if (lens[u][v] != INF) edges.add(new Edge(u, v, lens[u][v]));
}
}
}
private static int find(int x) {
if (p[x] == x) return x;
return p[x] = find(p[x]);
}
private static boolean union(int a, int b) {
int ra = find(a), rb = find(b);
if (ra == rb) return false;
if (s[ra] < s[rb]) {
int t = ra;
ra = rb;
rb = t;
}
p[rb] = ra;
s[ra] += s[rb];
return true;
}
private static int kruskal() {
Collections.sort(edges);
int mstCost = 0;
int usedEdges = 0;
for (Edge e : edges) {
if (union(e.u, e.v)) {
mstCost += e.w;
if (++usedEdges == label - 3) {
return mstCost;
}
}
}
return -1;
}
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());
m = Integer.parseInt(st.nextToken());
map = new int[n][m];
visited = new boolean[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());
}
}
grouping();
make();
buildEdges();
System.out.println(kruskal());
}
}
class Edge implements Comparable<Edge> {
int u, v, w;
Edge (int u, int v, int w) {
this.u = u;
this.v = v;
this.w = w;
}
@Override
public int compareTo(Edge o) {
return Integer.compare(this.w, o.w);
}
}