[백준 JAVA] 14466 : 소가 길을 건너간 이유 6
2024. 7. 28. 17:58ㆍ백준
[백준 14466] 소가 길을 건너간 이유 6 : https://www.acmicpc.net/problem/14466
문제 조건 정리
- 정사각형 목초지가 N×N (2 ≤ N ≤ 100) 격자로 이루어져 있다.
- 인접한 목초지 사이는 일반적으로 자유롭게 건너갈 수 있지만, 그 중 일부는 길을 건너야 한다. -> 사실상 (길 == 벽)
- 농장의 바깥에는 높은 울타리가 있어서 소가 농장 밖으로 나갈 일은 없다. -> 테두리 검사해야함
- K마리의 (1 ≤ K ≤ 100,K ≤ N2) 소가 존의 농장에 있고, 각 소는 서로 다른 목초지에 있다.
- 어떤 두 소는 길을 건너지 않으면 만나지 못 할 수 있다. 이런 소가 몇 쌍인지 세어보자. -> 쌍 공식 -> K*(K-1)/2
입력
- 첫 줄에 N, K, R이 주어진다.
- 다음 R줄에는 한 줄에 하나씩 길이 주어진다.
길은 상하좌우로 인접한 두 목초지를 잇고, -> "인접" 이라는 키워드 : abs(r - r') + abs(c-c') == 1,
r c r′ c′의 형태 (행, 열, 행, 열)로 주어진다.
각 수는 1 이상 N 이하이다. - 그 다음 K줄에는 한 줄의 하나씩 소의 위치가 행과 열로 주어진다.
문제를 풀면서
- 길, 사실상 문제의 벽을 어떻게 표현할지에 대해서, 상당한 수정을 거치면서 문제를 풀었음. 효율적으로 해결하기 위해서.
boolean[][] 으로 길을 표현
-> 상하좌우를 따질 수 없음
boolean[][] 으로 길을 표현, List<>에 길의 출발 지점과 도착 지점을 짝지음
-> 비효율적.. 출발 지점의 인덱스를 알 수 없으니, 탐색을 통해서 찾아야함
int[][] 으로 길을 표현
-> 상하좌우 값을 어떻게 설정해야할지 모르겠음, 리스트를 붙여도 비효율적
Map<int[], int[]> 으로 길을 표현
-> int[]은 객체로서 값의 주소로 표현됨, 값이 같아도 키로서 역할을 못함
List<List<Integer>> 으로 길을 표현
-> 비효율적, 탐색이 필요함
이외에도 문자열로 표현을 하는 등, 여러 방법을 생각하거나 시도해봤으나 맘에 들지 않았다.
그때 머리속을 스치는 생각, 비트마스킹!
이전에 풀었던 백준의 성곽 문제(https://www.acmicpc.net/problem/2234)가 생각났다.
성곽 문제에서는 진작에 입력으로 비트마스킹하게 사용하라고 힌트를 줬었다.
(벽에 대한 정보는 한 정수로 주어지는데, 서쪽에 벽이 있을 때는 1을, 북쪽에 벽이 있을 때는 2를, 동쪽에 벽이 있을 때는 4를, 남쪽에 벽이 있을 때는 8을 더한 값이 주어진다. 참고로 이진수의 각 비트를 생각하면 쉽다. 따라서 이 값은 0부터 15까지의 범위 안에 있다.)
알고 보면 간단한데 이를 위해서 얼마나 고생했나..
본 문제에서도 길을 벽으로 생각하고, 상하좌우를 1, 2, 4, 8의 값으로 int[][]에 저장하는게 맞았고, 그걸 유도했다고 생각한다.
성곽 문제와 다르게 힌트는 없었지만. - 위 내용의 구현은 헷갈릴 수 있지만 간단했다.
헷갈릴 수 있는 내용은 현재 내 위치를 기준으로 아래로 길(벽)이 있다면, 내 위치의 아래에서는 위에 길(벽)이 있다고 표시해야한다는 점이다. 그래프에서 인접 리스트를 구현할 때와 동일하다고 할 수 있다. - 비트마스킹은 +=으로 하기보다 |=으로 하는게 당연히 더 좋다.
자세한 내용은 아래 코드에서 확인할 수 있다. - 마지막으로 길을 건너지 않으면 만날 수 없는 소의 쌍은 전체 쌍을 구하고 만날 수 있는 쌍의 수를 뺐다.
k*k-1/2를 활용했다. 자세한 설명은 키워드(조합, 이항계수)를 검색해보면 된다!
코드
package baekJoon;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
public class BJ14466 {
static int[] dx = {-1, 1, 0, 0}; // 상, 하, 좌, 우
static int[] dy = {0, 0, -1, 1}; // 상, 하, 좌, 우
static int n;
static int[][] roadInfo;
public static void main(String[] args) throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer stringTokenizer = new StringTokenizer(bufferedReader.readLine(), " ");
n = Integer.parseInt(stringTokenizer.nextToken());
int k = Integer.parseInt(stringTokenizer.nextToken());
int r = Integer.parseInt(stringTokenizer.nextToken());
roadInfo = new int[n + 1][n + 1];
int[][] cowChecker = new int[n + 1][n + 1];
for (int i = 0; i < r; i++) {
stringTokenizer = new StringTokenizer(bufferedReader.readLine(), " ");
int sr = Integer.parseInt(stringTokenizer.nextToken());
int sc = Integer.parseInt(stringTokenizer.nextToken());
int er = Integer.parseInt(stringTokenizer.nextToken());
int ec = Integer.parseInt(stringTokenizer.nextToken());
if (sr == er) {
if (sc < ec) { // 오른쪽
roadInfo[sr][sc] |= 8; // 오른쪽 방향
roadInfo[er][ec] |= 4; // 왼쪽 방향
} else { // 왼쪽
roadInfo[sr][sc] |= 4; // 왼쪽 방향
roadInfo[er][ec] |= 8; // 오른쪽 방향
}
} else if (sc == ec) {
if (sr < er) { // 아래쪽
roadInfo[sr][sc] |= 2; // 아래쪽 방향
roadInfo[er][ec] |= 1; // 위쪽 방향
} else { // 위쪽
roadInfo[sr][sc] |= 1; // 위쪽 방향
roadInfo[er][ec] |= 2; // 아래쪽 방향
}
}
}
for (int i = 0; i < k; i++) {
stringTokenizer = new StringTokenizer(bufferedReader.readLine(), " ");
int row = Integer.parseInt(stringTokenizer.nextToken());
int col = Integer.parseInt(stringTokenizer.nextToken());
cowChecker[row][col] = i + 1;
}
boolean[][] visited = new boolean[n + 1][n + 1];
List<List<Integer>> cowsIdxList = new ArrayList<>();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (visited[i][j]) continue;
List<Integer> cowsIdx = new ArrayList<>();
Queue<int[]> queue = new ArrayDeque<>();
queue.offer(new int[]{i, j});
visited[i][j] = true;
while (!queue.isEmpty()) {
int[] curr = queue.poll();
if (cowChecker[curr[0]][curr[1]] != 0) {
cowsIdx.add(cowChecker[curr[0]][curr[1]]);
}
for (int d = 0; d < 4; d++) {
int nx = curr[0] + dx[d];
int ny = curr[1] + dy[d];
if (!isSafe(nx, ny)) continue;
if (visited[nx][ny]) continue;
if ((roadInfo[curr[0]][curr[1]] & (1 << d)) != 0) continue; // 이동 불가능한 방향 체크
queue.offer(new int[]{nx, ny});
visited[nx][ny] = true;
}
}
if (!cowsIdx.isEmpty()) {
cowsIdxList.add(cowsIdx);
}
}
}
// for (List<Integer> ci : cowsIdxList) {
// for (int i : ci) {
// System.out.print(i + " ");
// }
// System.out.println();
// }
// 각 구역의 소 수
List<Integer> groupSizes = new ArrayList<>();
for (List<Integer> ci : cowsIdxList) {
groupSizes.add(ci.size());
}
// 전체 쌍의 수
int totalPairs = (k * (k - 1)) / 2;
// 같은 구역 내 쌍의 수
int sameGroupPairs = 0;
for (int size : groupSizes) {
if (size > 1) {
sameGroupPairs += (size * (size - 1)) / 2;
}
}
// 구역이 다른 소들 간의 쌍의 수
int differentGroupPairs = totalPairs - sameGroupPairs;
System.out.println(differentGroupPairs);
}
private static boolean isSafe(int nx, int ny) {
return 1 <= nx && nx <= n && 1 <= ny && ny <= n;
}
}
// 2 3
// 1 4
// 5 6
문제를 풀고 난 후
고민을 통해서 올바른 문제 해결 방법을 떠올릴 수 있어서 행복했다.
BFS를 많이 풀어봤는데 비트마스킹 기법을 사용한 경험이 많지 않았다.
최근에 성곽 문제를 풀면서 비트연산자를 조금 공부한 덕분에 코드를 깔끔하게 작성할 수 있었다.
'백준' 카테고리의 다른 글
[백준 JAVA] 22954 : 그래프 트리 분할 (0) | 2024.08.10 |
---|---|
[백준 JAVA] 1135 : 뉴스 전하기 (0) | 2024.08.01 |
[이분탐색] Upper / Lower Bound (1) | 2024.07.24 |
[백준 JAVA] 8980 : 택배 (3) | 2024.07.20 |
[백준 JAVA] 15989 : 1, 2, 3 더하기 4 (0) | 2024.07.16 |