백준

Q3_1780(종이의 개수)

mozzi329 2022. 9. 2. 10:18
728x90

📌 문제

문제)
종이의 개수 N×N크기의 행렬로 표현되는 종이가 있다. 종이의 각 칸에는 -1, 0, 1 중 하나가 저장되어 있다.
우리는 이 행렬을 다음과 같은 규칙에 따라 적절한 크기로 자르려고 한다.
만약 종이가 모두 같은 수로 되어 있다면 이 종이를 그대로 사용한다.
(1)이 아닌 경우에는 종이를 같은 크기의 종이 9개로 자르고, 각각의 잘린 종이에 대해서 (1)의 과정을 반복한다.

이와 같이 종이를 잘랐을 때
-1로만 채워진 종이의 개수,
0으로만 채워진 종이의 개수,
1로만 채워진 종이의 개수를 구해내는 프로그램을 작성하시오.
입력)
첫째 줄에 N(1 ≤ N ≤ 37, N은 3k 꼴)이 주어진다.
다음 N개의 줄에는 N개의 정수로 행렬이 주어진다.
출력)
첫째 줄에 -1로만 채워진 종이의 개수를,
둘째 줄에 0으로만 채워진 종이의 개수를,
셋째 줄에 1로만 채워진 종이의 개수를 출력한다.
시간 제한 : 2초
메모리 제한 256MB

📌 문제 분석❗️

❗️N은 3의 제곱 형태로 들어온다. (N = 3^k)

 

 

❗️전체 영역을 9등분해나가며 색종이 여부를 파악한다.

 

 

❗️값은 -1, 0, 1인 경우를 카운트한다.

  • 탐색은 N x N 배열을 9등분하여 진행한다.
  • 탐색 범위의 값이 전부 -1이라면 -1 변수에 +1 해준다.
  • 탐색 범위의 값이 전부 0이라면 0 변수에 +1 해준다.
  • 탐색 범위의 값이 전부 1이라면 1 변수에 +1 해준다.
  • -1, 0, 1이 섞여있다면, 해당 범위를 다시 9등분하여 탐색한다. (더이상 쪼개지지 않을 때까지)
    조건에 따라 카운트해준다.

📌 진행

❗️전역 변수 선언

private static int[][] paperArr;
private static int[] result = new int[3];
  • paperArr : 입력받은 배열을 저장할 전역 변수 배열
  • result : 카운트한 결과 값을 입력받는 배열
         - result[0] : -1 색종이를 카운트한 값
         - result[1] : 0 색종이를 카운트한 값
         - result[2] : 1 색종이를 카운트한 값

 

❗️체크 메서드 생성

private static boolean checked(int row, int col, int divisionN) {
    int firstValue = paperArr[row][col];
    for (int i = row; i < row + divisionN; i++) {
        for (int j = col; j < col + divisionN; j++) {
            if (firstValue != paperArr[i][j]) return false;
        }
    }
    return true;
}

값을 확인하기 위한 체크 메서드를 생성한다. 처음 값을 firstValue 변수에 담아준다.

해당 범위의 값이 모두 동일하다면 모든 값이 firstValue와 동일할테니 통과한다면 true를, 다르다면 false를 리턴.

 

 

❗️재귀를 통한 분할 정복

private static void divideAndConquer(int row, int col, int N) {
    int inputPaperValue = paperArr[row][col];
    if (checked(row, col, N)) {
           if (inputPaperValue == -1) result[0] ++;
           else if (inputPaperValue == 0) result[1] ++;
           else result[2] ++;
           return;
    }
    int divisionN = N/3;

    // 9등분
    // 1
    divideAndConquer(row, col, divisionN);
    // 2
    divideAndConquer(row, col + divisionN, divisionN);
    // 3
    divideAndConquer(row, col + 2 * divisionN, divisionN);
    // 4
    divideAndConquer(row + divisionN, col, divisionN);
    // 5
    divideAndConquer(row + divisionN, col + divisionN, divisionN);
    // 6
    divideAndConquer(row + divisionN, col + 2 * divisionN, divisionN);
    // 7
    divideAndConquer(row + 2 * divisionN, col, divisionN);
    // 8
    divideAndConquer(row + 2 * divisionN, col + divisionN, divisionN);
    // 9
    divideAndConquer(row + 2 * divisionN, col + 2 * divisionN, divisionN);

}
  • 범위 안의 값이 모두 동일하다면(checked 메서드를 만족)
     → -1 색종이일 경우 : result[0] + 1
     → 0 색종이일 경우 : result[1] + 1
     → 1 색종이일 경우 : result[2] + 1
    그리고 리턴으로 재귀를 종료해준다.
  • 값이 다르다면 범위를 9등분하여 재귀 함수를 호출한다.

📌 전체 코드

package level21_분할정복_.Q3_1780;
import java.io.IOException;

public class Main {
    private static int[][] paperArr;
    private static int[] result = new int[3];

    public static void main(String[] args) throws IOException{
        int N = readInt();
        paperArr = new int[N][N];
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                paperArr[i][j] = readInt();
            }
        }
        divideAndConquer(0, 0, N);
        System.out.println(result[0]);
        System.out.println(result[1]);
        System.out.println(result[2]);
    }

    private static boolean checked(int row, int col, int divisionN) {
        int firstValue = paperArr[row][col];
        for (int i = row; i < row + divisionN; i++) {
            for (int j = col; j < col + divisionN; j++) {
                if (firstValue != paperArr[i][j]) return false;
            }
        }
        return true;
    }

    private static void divideAndConquer(int row, int col, int N) {
        int inputPaperValue = paperArr[row][col];
        if (checked(row, col, N)) {
               if (inputPaperValue == -1) result[0] ++;
               else if (inputPaperValue == 0) result[1] ++;
               else result[2] ++;
               return;
        }
        int divisionN = N/3;

        // 9등분
        // 1
        divideAndConquer(row, col, divisionN);
        // 2
        divideAndConquer(row, col + divisionN, divisionN);
        // 3
        divideAndConquer(row, col + 2 * divisionN, divisionN);
        // 4
        divideAndConquer(row + divisionN, col, divisionN);
        // 5
        divideAndConquer(row + divisionN, col + divisionN, divisionN);
        // 6
        divideAndConquer(row + divisionN, col + 2 * divisionN, divisionN);
        // 7
        divideAndConquer(row + 2 * divisionN, col, divisionN);
        // 8
        divideAndConquer(row + 2 * divisionN, col + divisionN, divisionN);
        // 9
        divideAndConquer(row + 2 * divisionN, col + 2 * divisionN, divisionN);

    }


    private static int readInt() throws IOException {
        boolean isNegative = false;
        int value = 0;
        while (true) {
            int input = System.in.read();
            if (input == ' ' || input == '\n') {
                return (isNegative) ? -1 * value :
                        value;
            } else if (input == '-') isNegative = true;
            else value = value * 10 + (input - 48);
        }
    }
}

📌 참조

분할 정복 알고리즘(나무위키) - 참조