Problem Solving/BOJ(백준)

[BOJ]백준 17136번: 색종이 붙이기

이진2 2019. 11. 12. 19:38

https://www.acmicpc.net/problem/17136

 

17136번: 색종이 붙이기

<그림 1>과 같이 정사각형 모양을 한 다섯 종류의 색종이가 있다. 색종이의 크기는 1×1, 2×2, 3×3, 4×4, 5×5로 총 다섯 종류가 있으며, 각 종류의 색종이는 5개씩 가지고 있다. <그림 1> 색종이를 크기가 10×10인 종이 위에 붙이려고 한다. 종이는 1×1 크기의 칸으로 나누어져 있으며, 각각의 칸에는 0 또는 1이 적혀 있다. 1이 적힌 칸은 모두 색종이로 덮여져야 한다. 색종이를 붙일 때는 종이의 경계 밖으로 나가서는 안되고, 겹쳐

www.acmicpc.net

캐슬 디펜스와 같이 A형 기출문제였던 색종이 붙이기 문제!!

 

처음에는 그냥 로직을

맵을 돌면서 가장 큰 색종이부터 작아지는 순서대로 붙이는 그리디한 방식으로 짰다.

하지만 그렇게 하면 치명적인 오류가 있었다.

<잘못된 코드>

#include <cstdio>
#include <memory.h>
int map[11][11], paper[6] = { 0,5,5,5,5,5 }, cnt[6], ans=50;
bool c[11][11], check;
bool iscover(int s, int row, int col) {
	for(int i=0;i<s;i++)
		for (int j = 0; j < s; j++)
			if (!map[row + i][col + j]||c[row+i][col+j])return false;
	for (int i = 0; i < s; i++)
		for (int j = 0; j < s; j++)
			c[row + i][col + j] = true;
	return true;
}
int main() {
	for (int i = 0; i < 10; i++)
		for (int j = 0; j < 10; j++) {
			scanf("%d", &map[i][j]);
			check |= map[i][j];
		}
	if (!check) {
		printf("0"); return 0;
	}
	for (int a = 1; a <= 5; a++) {
		int sum = 0; bool possible = true;
		for (int b = a; b >= 1; b--) {

			for (int i = 0; i < 10; i++)
				for (int j = 0; j < 10; j++)
					if (map[i][j]&&!c[i][j]) {
						check = iscover(b, i, j);
						if (check)cnt[b]++;
					}

			if (cnt[b] > 5) { possible = false; break; }
			sum += cnt[b];
		}

		if (possible && sum && sum < ans)ans = sum;
		memset(c, false, sizeof(c));
		memset(cnt, 0, sizeof(cnt));
	}
	printf("%d", ans == 50 ? -1 : ans);
	return 0;
}

 

이런 테이블이 존재한다고 가정해보자. 만약 5*5 색종이부터 4*4, 3*3, ... 이런식으로 붙인다면?

이런 형태가 우선적으로 만들어지겠지만, 남아있는 칸은 1*1 7장으로 채워야 하기 때문에 거를 수 있다.

위의 코드에서는 답이 12장으로 나오는데, 그 때 형태는 아래와 같다.

하지만 처참하게 틀렸다. 그리디하게 풀면 안 되고 특정 색종이를 붙였다고 가정할 때, 안 붙였다고 가정할 때를 이용한 백트래킹(완전탐색)을 이용해줘야 했다. 그래서 위의 그림은 아래와 같은 형태가 정답이다.

기억하자. 삼성 역량테스트&코딩테스트는 완전탐색, 구현, BFS라는 걸.....

 

그래서 테이블의 행과 열을 인자로 하는 백트래킹 함수를 만들어, 특정 색종이를 붙일지or안 붙일지를 고려해주는 백트래킹 방식으로 다시 구현했다.

#include <cstdio>
#include <memory.h>
int map[25][25], cnt, paper[6], ans = 50;
bool cvr[11][11], check;
bool iscover(int s, int row, int col) {
	if (row + s > 10 || col + s > 10)return false;
	for (int i = 0; i < s; i++)
		for (int j = 0; j < s; j++)
			if (!map[row + i][col + j] || cvr[row + i][col + j])return false;
	for (int i = 0; i < s; i++)
		for (int j = 0; j < s; j++)
			cvr[row + i][col + j] = true;
	return true;
}
void recover(int s, int row, int col) {
	for (int i = 0; i < s; i++)
		for (int j = 0; j < s; j++)
			cvr[row + i][col + j] = false;
}
void func(int r, int c, int s) {
	for (int i = 1; i <= 5; i++)
		if (paper[i] > 5)return;

	if (c >= 10) { 
		func(r + 1, 0, s); return; 
	}
	if (r >= 10||s==cnt) { 
		int sum = 0;
		for (int i = 1; i <= 5; i++)sum += paper[i];
		if (sum < ans)ans = sum;
		return; 
	}

	if (!map[r][c] || cvr[r][c]) {
		func(r, c + 1, s);
		return;
	}

	for (int i = 5; i > 0; i--) {
		check = iscover(i, r, c);
		if (check) {
			paper[i]++;

			func(r, c + i, s+i*i);

			paper[i]--;
			recover(i, r, c);
		}
	}
	return;
}
int main() {
	for (int i = 0; i < 10; i++)
		for (int j = 0; j < 10; j++) {
			scanf("%d", &map[i][j]);
			check |= map[i][j];
			cnt += map[i][j];
		}
	if (!check) {
		printf("0"); return 0;
	}
	func(0,0,0);
	printf("%d", ans == 50 ? -1 : ans);
	return 0;
}