본문 바로가기

알고리즘

[백준] 스티커 붙이기 18808 [ Java ]

반응형

출처 : https://www.acmicpc.net/problem/18808

 

18808번: 스티커 붙이기

혜윤이는 최근에 다양한 대회를 참여하면서 노트북에 붙일 수 있는 스티커들을 많이 받았다. 스티커는 아래와 같이 사각 모눈종이 위에 인쇄되어 있으며, 스티커의 각 칸은 상하좌우로 모두 연�

www.acmicpc.net

문제

혜윤이는 최근에 다양한 대회를 참여하면서 노트북에 붙일 수 있는 스티커들을 많이 받았다. 스티커는 아래와 같이 사각 모눈종이 위에 인쇄되어 있으며, 스티커의 각 칸은 상하좌우로 모두 연결되어 있다. 또한 모눈종이의 크기는 스티커의 크기에 꼭 맞아서, 상하좌우에 스티커가 포함되지 않는 불필요한 행이나 열이 존재하지 않는다.

아래는 올바른 모눈종이의 예시이다. 주황색 칸은 스티커가 붙은 칸을, 하얀색 칸은 스티커가 붙지 않은 칸을 나타낸다.

 

반면 아래는 올바르지 않은 모눈종이의 예시이다. 첫 번째는 윗쪽에 불필요한 행이 있고, 두 번째는 왼쪽에 불필요한 열이 있다. 그리고 세 번째는 스티커의 각 칸이 상하좌우로 모두 연결되어 있지 않다.

 

 

혜윤이는 자신의 노트북에 이 스티커들을 붙이기로 했다. 혜윤이의 노트북은 마침 직사각형 모양이고, 스티커가 인쇄된 모눈종이와 같은 간격으로 격자가 그려져 있다. 혜윤이는 스티커들을 먼저 받았던 것부터 차례대로 격자에 맞춰서 붙이려고 한다.

혜윤이가 스티커를 붙이는 방법은 다음과 같다.

  1. 스티커를 회전시키지 않고 모눈종이에서 떼어낸다.
  2. 다른 스티커와 겹치거나 노트북을 벗어나지 않으면서 스티커를 붙일 수 있는 위치를 찾는다. 혜윤이는 노트북의 위쪽부터 스티커를 채워 나가려고 해서, 스티커를 붙일 수 있는 위치가 여러 곳 있다면 가장 위쪽의 위치를 선택한다. 가장 위쪽에 해당하는 위치도 여러 곳이 있다면 그중에서 가장 왼쪽의 위치를 선택한다.
  3. 선택한 위치에 스티커를 붙인다. 만약 스티커를 붙일 수 있는 위치가 전혀 없어서 스티커를 붙이지 못했다면, 스티커를 시계 방향으로 90도 회전한 뒤 2번 과정을 반복한다.
  4. 위의 과정을 네 번 반복해서 스티커를 0도, 90도, 180도, 270도 회전시켜 봤음에도 스티커를 붙이지 못했다면 해당 스티커를 붙이지 않고 버린다.

아래의 예시를 통해 스티커를 붙이는 과정을 이해해보자. 노트북은 세로 5칸, 가로 4칸 크기이고, 혜윤이가 가지고 있는 스티커들은 아래와 같다. 왼쪽에서 오른쪽 순으로 스티커를 붙일 것이다.

 

1. 첫 번째 스티커는 회전 없이 온전히 붙일 수 있는 공간이 아래와 같이 6곳이 있다.

이 중에서 가장 위쪽의 위치, 가능한 가장 위쪽의 위치가 여러 개이면 그 중에서 가장 왼쪽의 위치는 첫 번째이다. 스티커를 붙인 후 노트북의 모양은 아래와 같다.

 

2. 두 번째 스티커는 회전 없이 온전히 붙일 수 있는 공간이 없다. 그러나 시계 방향으로 90도 회전한 후에는 붙일 수 있는 공간이 1개 생긴다. 해당 공간에 스티커를 붙인 후 노트북의 모양은 아래와 같다.

 

 3. 세 번째 스티커는 스티커를 시계방향으로 0, 90, 180, 270도 회전시킨 모양에 대해 전부 확인을 해도 스티커를 붙일 수있는 공간이 없다. 그러므로 해당 스티커를 붙이지 않고 버린다.

 

 4.네 번째 스티커는 스티커를 시계방향으로 0, 90, 180도 회전 시킨 모양에 대해 온전히 붙일 수 있는 공간이 없다. 그러나 시계 방향으로 270도 회전한 후에는 공간이 1개 생긴다. 스티커를 붙인 후 노트북의 모양은 아래와 같다. 최종적으로 노트북의 18칸이 스티커로 채워졌다.

혜윤이는 스티커를 다 붙인 후의 노트북의 모습이 궁금해졌다. 노트북의 크기와 스티커들이 주어졌을 때 스티커들을 차례대로 붙이고 난 후 노트북에서 몇 개의 칸이 채워졌는지 구해보자.

입력

첫째 줄에 노트북의 세로와 가로 길이를 나타내는 N(1 ≤ N ≤ 40)과 M(1 ≤ M ≤ 40), 그리고 스티커의 개수 K(1 ≤ K ≤ 100)이 한 칸의 빈칸을 사이에 두고 주어진다.

그 다음 줄부터는 K개의 스티커들에 대한 정보가 주어진다. 각 스티커는 아래와 같은 형식으로 주어진다.

먼저 i번째 스티커가 인쇄된 모눈종이의 행의 개수와 열의 개수를 나타내는 Ri(1 ≤ Ri ≤ 10)와 Ci(1 ≤ Ci ≤ 10)가 한 칸의 빈칸을 사이에 두고 주어진다.

다음 Ri개의 줄에는 각 줄마다 모눈종이의 각 행을 나타내는 Ci개의 정수가 한 개의 빈칸을 사이에 두고 주어진다. 각 칸에 들어가는 값은 0, 1이다. 0은 스티커가 붙지 않은 칸을, 1은 스티커가 붙은 칸을 의미한다.

문제에서 설명한 것과 같이 스티커는 모두 올바른 모눈종이에 인쇄되어 있다. 구체적으로 스티커의 각 칸은 상하좌우로 모두 연결되어 있고, 모눈종이의 크기는 스티커의 크기에 꼭 맞아서 상하좌우에 스티커에 전혀 포함되지 않는 불필요한 행이나 열이 존재하지 않는다.

출력

첫째 줄에 주어진 스티커들을 차례대로 붙였을 때 노트북에서 스티커가 붙은 칸의 수를 출력한다.

 

풀이 아이디어 

- 시뮬레이션 문제였고, 순차적으로 스티커를 3차원 배열에 저장한 후에 , 꺼내서 붙이는 연산을 했다.

개선 --> 3차원 배열에 저장하지 않고, 입력 받음과 동시에 스티커를 하나씩 붙인다면, 메모리 공간을 줄일 수 있을 것같다.

 

문제점

- 1. 먼저 회전 없이 스티커를 하나씩 붙이기

- 2. 추가적으로 회전 해서 코드를 수정하려다보니 부분적인 수정이 많이 필요했다.

- 3. 처음부터 알고리즘을 설계하고 코드 개요를 명확히 해서 오류없이 짜는 것이 중요하다.

 

JAVA 코드

import java.awt.Point;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class 스티커붙이기_18808 {
	static int[][] notebook;
	static int N, M, K; // N = row , M = column , K = counts of sticker
	static int[][][] stickers;
	static int[][] sticker;
	static int ans = 0;

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		Queue<Point> queue = new LinkedList<>();
		// ArrayList<Sticker> KindofSticker = new ArrayList<>();
		N = sc.nextInt();
		M = sc.nextInt();
		K = sc.nextInt();

		notebook = new int[N][M];
		stickers = new int[K][][];

		for (int i = 0; i < K; i++) {
			int r = sc.nextInt();
			int c = sc.nextInt();
			stickers[i] = new int[r][c];
			for (int j = 0; j < r; j++) {
				for (int k = 0; k < c; k++) {
					stickers[i][j][k] = sc.nextInt();
				}
			}
		}
		boolean isok = true;

		// 첫번째 스티커 붙이기
		// 처음 위치 부터 보자.
		for (int Curnum = 0; Curnum < K; Curnum++) {

			nextSticker: for (int degree = 0; degree <= 270; degree += 90) { // 90도씩 4번 화전

				sticker = rotate(stickers[Curnum], degree);
				int CR = sticker.length;
				int CC = sticker[0].length;
				if (CR > N || CC > M) {

					continue; // 스티커의 크기가 노트북크기보다 크면 일다 넘어가자.
				}
				for (int i = 0; i < N; i++) { // 노트북 행
					for (int j = 0; j < M; j++) { // 노트북 열
						isok = true; // initial

						nextpos: for (int k = i; k < i + CR; k++) { // 노트북 기준 추가적인 행/열
							for (int l = j; l < j + CC; l++) { // 노트북 기준 추가적인 행/열

								if (k >= N || l >= M) {

									// 노트북 i, j열부터 탐색하는데 k행 l열이 노트북의 크기를 초과한다면 바운더리 체크
									isok = false; // 채울곳에 빈칸아니라면 다음 칸 부터 찾기
									break nextpos;
								}

								if (sticker[k - i][l - j] == 0 || sticker[k - i][l - j] == 1 && notebook[k][l] == 0) {
									// 첫번째 스티커 모양 채울곳에 노트북에 빈 모양이라면
									isok = true;
									continue; // 다음으로 넘어가 잘하고 있으니까
								}

								// 하나 걸치는게 있으면? == 안맞는게 있으면 next i, j 로 이동 ~
								else {

									isok = false; // 채울곳에 빈칸아니라면 다음 칸 부터 찾기
									break nextpos;
								}

							}
						}
						// 하나도 안걸치면?
						// 스티커 붙이자.
						if (isok) {

							for (int startR = i; startR < i + CR; startR++) {
								for (int startC = j; startC < j + CC; startC++) {
									if (sticker[startR - i][startC - j] == 1) {
										ans++;
										notebook[startR][startC] = sticker[startR - i][startC - j];
									}
								}
							}

							break nextSticker; // 다음스티커
						}

					}
				}
			}

		}

		System.out.println(ans);
	}

	static int[][] rotate(int[][] arr, int degree) {
		int[][] rotate;
		int n = arr.length; // 행
		int m = arr[0].length; // 열
		switch (degree) {
		case 0:
			rotate = new int[n][m];
			break;
		case 90:
			rotate = new int[m][n];
			break;
		case 180:
			rotate = new int[n][m];
			break;
		case 270:
			rotate = new int[m][n];
			break;
		default:
			throw new IllegalArgumentException();
		}

		for (int i = 0; i < rotate.length; i++) {
			for (int j = 0; j < rotate[i].length; j++) {
				switch (degree) {
				case 0:
					rotate[i][j] = arr[i][j]; // 그대로
					break;
				case 90:
					rotate[i][j] = arr[n - 1 - j][i];
					break;
				case 180:
					rotate[i][j] = arr[n - 1 - i][m - 1 - j];
					break;
				case 270:
					rotate[i][j] = arr[j][m - 1 - i];
					break;
				}
			}
		}

		return rotate;
	}
}

 

반응형