1+ #include < algorithm>
2+ #include < cstring> // memset 사용을 위해 추가
3+ #include < iostream>
4+ #include < utility>
5+ #include < vector>
6+
7+ using namespace std ;
8+
9+ int boardH, boardW;
10+ int R, C;
11+ char board[10 ][10 ];
12+ vector<vector<char >> block;
13+
14+ // 블럭 회전을 위한 값
15+ vector<vector<pair<int , int >>> rotations;
16+
17+ // 가지치기를 위한 block 사이즈
18+ int blockSize = -1 ;
19+
20+ // 구현 1 : 블럭을 돌리는 과정
21+ // 90도 회전은 행렬연산을 통해서 계산하면 된다
22+ // [ 0 -1 ] [ x ] [ -y ]
23+ // [ 1 0 ] [ y ] [ x ] 자고로 반시계 회전이다
24+ // 원래 좌표에서 x >> -y 로 y >> x로 변환
25+
26+ // 블럭은 계산을 위해 상대 좌표로 표현되어야 한다.
27+ // 첫 번째 만나는 #을 기준으로 얼마나 떨어져있냐를 표현한다.
28+ void makeRotations (vector<vector<char >> &block) {
29+ rotations.clear ();
30+ rotations.resize (4 );
31+
32+ for (int rot = 0 ; rot < 4 ; ++rot) {
33+ int firstX = -1 , firstY = -1 ;
34+
35+ // ★ 현재 회전 상태의 실제 크기를 사용해야 한다 (R,C 사용 금지)
36+ int H = (int )block.size ();
37+ int W = H ? (int )block[0 ].size () : 0 ;
38+
39+ for (int i = 0 ; i < H; i++) {
40+ for (int j = 0 ; j < W; j++) {
41+ // 처음 만난 #을 (0,0)으로 지정하는 상대좌표의 블럭 형태로 변환
42+ if (block[i][j] == ' #' ) {
43+ if (firstY == -1 ) {
44+ firstY = i;
45+ firstX = j;
46+ }
47+
48+ // 위 캡션에서 설명한대로 상대좌표로 구현
49+ rotations[rot].push_back (make_pair (i - firstY, j - firstX));
50+ }
51+ }
52+ }
53+
54+ // ★ 중복 제거 정확도를 위해 각 회전 모양의 좌표들을 정렬해 정규화한다
55+ sort (rotations[rot].begin (), rotations[rot].end ());
56+
57+ // 기존의 블럭을 회전 시킨다.
58+ // 이것은 함수 위의 캡션을 보면 이해가능
59+ vector<vector<char >> dst (W, vector<char >(H, ' ' ));
60+ for (int i = 0 ; i < H; ++i) {
61+ for (int j = 0 ; j < W; ++j) {
62+ dst[j][H - 1 - i] = block[i][j];
63+ }
64+ }
65+
66+ block = dst;
67+ }
68+
69+ // 모든 블럭이 회전된 상태로 넣어졌을 때, 중복된 블럭들을 제거해야한다
70+ sort (rotations.begin (), rotations.end ());
71+ rotations.erase (unique (rotations.begin (), rotations.end ()), rotations.end ());
72+
73+ blockSize = rotations.empty () ? 0 : (int )rotations[0 ].size ();
74+ }
75+
76+ // #, . 로 이루어진 board는 0과 1 그리고 그 이상으로 할 수 있게 구현
77+ int intBoard[10 ][10 ];
78+
79+ // 구현 3 : 이 블럭을 둘 수 있는지 확인하는 함수
80+ bool set (int y, int x, const vector<pair<int , int >> &block, int delta) {
81+ int result = true ;
82+
83+ for (int i = 0 ; i < (int )block.size (); i++) {
84+ // ★ 기준 좌표 (y,x)를 반드시 더해 절대좌표로 변환해야 한다
85+ int ny = y + block[i].first ;
86+ int nx = x + block[i].second ;
87+
88+ // 좌표가 보드를 넘었다면
89+ if (ny >= boardH || ny < 0 || nx >= boardW || nx < 0 ) {
90+ result = false ;
91+ } else if ((intBoard[ny][nx] += delta) >
92+ 1 ) { // 놓았을 때 1 초과 인 것을 검사
93+ result = false ;
94+ }
95+ }
96+
97+ return result;
98+ }
99+
100+ // 구현 2: 회전된 블럭들로 board에 블럭을 두기
101+ // 완전 탐색 + 가지치기를 하면서 최대한 빠르게 구해본다.
102+ // 각 상태마다는 두어진 블럭을 가지고 있다.
103+ // 두어진 블럭의 개수가 최대가 되게끔 해보자.
104+
105+ // 모든 경우의 수에서 최고의 답을 저장
106+ int best = -1 ;
107+
108+ // 가지치기를 구현
109+ // 과대평가를 해서 상한선을 정해야한다.
110+ // 어차피 현재 placed + (남은칸의수)/(블럭) 이 best보다 낮다면 볼 필요도 없다
111+ bool pruning (int placed) {
112+ int empties = 0 ;
113+ for (int i = 0 ; i < boardH; i++) {
114+ for (int j = 0 ; j < boardW; j++) {
115+ if (intBoard[i][j] == 0 ) {
116+ ++empties;
117+ }
118+ }
119+ }
120+
121+ return placed + (empties / blockSize) <= best ? true : false ;
122+ }
123+
124+ void search (int placed) {
125+ // 초반에 가지치기를 계산해본다.
126+ if (pruning (placed))
127+ return ;
128+
129+ // 비어있는 칸을 찾는다.
130+ int firstY = -1 , firstX = -1 ;
131+ // ★ 보드 순회는 boardH/boardW를 사용해야 한다 (R,C 사용 금지)
132+ for (int i = 0 ; i < boardH; i++) {
133+ int isBreak = -1 ;
134+ for (int j = 0 ; j < boardW; j++) {
135+ if (intBoard[i][j] == 0 ) {
136+ isBreak = 1 ;
137+ firstY = i;
138+ firstX = j;
139+
140+ break ;
141+ }
142+ }
143+ if (isBreak == 1 )
144+ break ;
145+ }
146+
147+ // 비어있는 칸이 없으면 종료
148+ if (firstX == -1 ) {
149+ best = max (best, placed);
150+ return ;
151+ }
152+
153+ // 비어있는 칸이 있을 경우 백트래킹을 통한 완전탐색
154+ for (int rot = 0 ; rot < (int )rotations.size (); ++rot) {
155+ // 둘 수 있는 건지 검사
156+ if (set (firstY, firstX, rotations[rot], 1 )) {
157+ // 둘 수 있는경우
158+ search (placed + 1 );
159+ }
160+ // 백트래킹 구현
161+ set (firstY, firstX, rotations[rot], -1 );
162+ }
163+
164+ // 최대의 개수라고 했으니
165+ // 해당 좌표에 그냥 두지 않았을 경우도 계산해봐야함
166+ intBoard[firstY][firstX] = 1 ;
167+ search (placed);
168+ intBoard[firstY][firstX] = 0 ;
169+ }
170+
171+ int solve () {
172+
173+ // board 를 intBoard로 바꾸는 작업 시작
174+ for (int i = 0 ; i < boardH; ++i) {
175+ for (int j = 0 ; j < boardW; ++j) {
176+ intBoard[i][j] = board[i][j] == ' #' ? 1 : 0 ;
177+ }
178+ }
179+ best = 0 ;
180+ search (0 );
181+
182+ return best;
183+ }
184+
185+ int main (void ) {
186+ ios::sync_with_stdio (0 );
187+ cin.tie (0 );
188+
189+ int cc;
190+ cin >> cc;
191+ for (int c = 0 ; c < cc; c++) {
192+ cin >> boardH >> boardW >> R >> C;
193+
194+ // 보드 초기화
195+ memset (board, ' .' , sizeof (board));
196+
197+ for (int i = 0 ; i < boardH; i++) {
198+ for (int j = 0 ; j < boardW; j++) {
199+ cin >> board[i][j];
200+ }
201+ }
202+
203+ // 블럭초기화
204+ block.assign (R, vector<char >(C, ' .' ));
205+
206+ for (int i = 0 ; i < R; i++) {
207+ for (int j = 0 ; j < C; j++) {
208+ cin >> block[i][j];
209+ }
210+ }
211+ makeRotations (block);
212+ cout << solve () << endl;
213+ }
214+
215+ return 0 ;
216+ }
0 commit comments