https://www.acmicpc.net/problem/17136
10X10 인 종이에 있는 1을 색종이를 사용하여 모두 덮는 문제이다.
종이의 크기가 10X10이고 사용할 수 있는 색종이가 최대 5X5개 이므로 완전 탐색이 가능하다
1이 있는 곳에 대해서 5가지의 색종이를 모두 붙여보고 최솟값을 구해주면 된다.
처음에는 그냥 큰 것부터 붙이면 되는 줄 알고 그렇게 구현했다가 틀렸었다.
큰 종이를 먼저 붙여버려서 애매하게 남은 칸이 발생해서 아예 색종이를 덮지 못하는 경우가 생길 수도 있기 때문이다
내가 구현한 방식은 먼저
- 배열에서 1인 곳(색종이를 덮어줘야 하는 곳)을 찾는다.
- 없다면 모두 채운 것이므로 현재까지 사용한 색종이의 수를 정답과 비교해주고 return 한다.
- 있다면 반복문을 break 해주고 1인 곳을 5가지의 색종이로 덮는 모든 경우를 해준다.
- 이제 각각의 색종이로 덮어보는 모든 경우를 해줄 것인데 k*k크기의 색종이로 덮는다고 했을 때, 처리해줘야 할 조건은 다음과 같다
- 해당 크기의 색종이가 남아있는지
- 해당 크기의 색종이로 1인 곳으로부터 k*k칸을 모두 덮을 수 있는지(중간에 0이 없는지)
- 추가적으로 위의 조건에서 10x10의 범위를 넘어가지 않는지도 잘 체크해주어야 한다.
- 위의 조건들을 통과해서 k*k크기의 색종이를 사용할 수 있다면 색종이 수를 하나 감소해준다.
- 그리고 k*k 크기만큼 0으로 만들어준다. 이때 새로운 배열을 만들어서 덮어줘야 한다.
- 현재 칸(1인 곳)에 대해서 1x1을 사용하는 경우에서부터 5x5를 사용하는 경우는 모두 다른 결과가 나오기 때문에 각각 새로 만들어서 다음 경우로 보내주어야 한다.
- 즉, 새로 만들어주지 않고 기존의 배열을 사용하면 이미 앞에서 사용한 색종이를 적용한 상태에서 또 덮어버리는 모양이 되기 때문이다.
- 이제 현재 k*k크기의 색종이로 덮어준 새로운 배열과 함께 사용한 색종이 숫자를 하나 증가시켜서 재귀 호출을 해준다.
- 재귀 호출에서 돌아온 경우에는 위에서 사용한 색종 이수를 다시 늘려준다. (현재 칸에 대해서 다른 색종이를 사용하는 경우를 해줘야 하기 때문)
(추가) 사용한 색종이 숫자의 최솟값을 찾는 문제이기 때문에 현재 사용한 색종 이수가 정답보다 커질 경우 더 탐색할 필요가 없으므로 43번째 줄에 if(usingnum >= ans) return;를 추가해주면 시간을 더 줄일 수 있다!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
#include <iostream>
using namespace std;
int papercnt[6];
int ans;
void mapcopy(int paper[10][10], int newpaper[10][10]) {
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
newpaper[i][j] = paper[i][j];
}
}
}
//종이를 k크기만큼 덮는다
void cover(int x, int y, int k, int paper[10][10]) {
for (int i = x; i < x + k; i++) {
for (int j = y; j < y + k; j++) {
paper[i][j] = 0;
}
}
}
//k*k크기의 색종이를 덮을 수 있는지 검사한다. 불가능할 경우 바로 false를 리턴
bool usePaper(int x, int y, int k, int paper[10][10]) {
for (int i = x; i < x + k; i++) {
for (int j = y; j < y + k; j++) {
//종이 범위 를 넘어가면 false
if (i >= 10 || j >= 10) return false;
//0이 있는 자리에는 색종이를 놓을 수 없다
if (paper[i][j] == 0) return false;
}
}
return true;
}
void solve(int paper[10][10], int usingnum) {
//색종이가 안덮인 곳을 찾는다.
bool flag = false;
int x, y;
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (paper[i][j] == 1) {
x = i;
y = j;
flag = true;
break;
}
}
if (flag) break;
}
//종이를 다 붙였다
if (!flag) {
if (ans > usingnum) ans = usingnum;
return;
}
//각각의 종류의 색종이를 사용하는 경우를 모두 구해준다
for (int k = 5; k > 0; k--) {
//해당 색종이가 없다
if (papercnt[k] == 0) continue;
//해당 종이로 채울 수 없다
if (!usePaper(x, y, k, paper)) continue;
//종이 사용
papercnt[k] -= 1;
//새로운 배열을 만들고
int newpaper[10][10];
mapcopy(paper, newpaper);
//덮는다
cover(x, y, k, newpaper);
//k*k색종이를 사용한 경우에 대해서 새로 탐색을 해준다.
//색종이 사용 횟수 증가
solve(newpaper, usingnum+1);
//다른 색종이를 사용하는 경우를 위해서 종이 다시 증가
papercnt[k] += 1;
}
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(0);
int paper[10][10];
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
cin >> paper[i][j];
}
}
//각 색종이는 처음에 5개씩 있다
for (int i = 1; i <= 5; i++) papercnt[i] = 5;
//최솟값을 찾기 위해 정답을 최댓값으로 초기화
ans = 26;
solve(paper, 0);
//ans값이 초기값이라면 1을 모두 덮는 것이 불가능한 경우다.
if (ans == 26) cout << -1 << '\n';
else cout << ans << '\n';
return 0;
}
Colored by Color Scripter
|
'BOJ' 카테고리의 다른 글
[BOJ] 16234. 인구 이동 (0) | 2019.08.02 |
---|---|
[BOJ] 17281. ⚾ (0) | 2019.08.02 |
[BOJ] 17135. 캐슬 디펜스 (0) | 2019.08.01 |
[BOJ] 16236. 아기 상어 (0) | 2019.07.31 |
[BOJ] 17144. 미세먼지 안녕! (0) | 2019.07.30 |