www.acmicpc.net/problem/2011

 

2011번: 암호코드

나올 수 있는 해석의 가짓수를 구하시오. 정답이 매우 클 수 있으므로, 1000000으로 나눈 나머지를 출력한다. 암호가 잘못되어 암호를 해석할 수 없는 경우에는 0을 출력한다.

www.acmicpc.net

<풀이>

1. 암호코드를 입력받는다.

2. 암호코드가 해석될 수 있는 가짓수를 구하고 출력한다.

 

<해법>

DP 문제라는 것을 쉽게 생각할 수 있습니다. 

DP 문제를 접근하는 방식으로 Top-down, Bottom-up 방식 이렇게 두 가지가 있는데, 두 방법을 모두 사용해서 풀어보겠습니다.

DP의 핵심은 "점화식 도출" 입니다.

Top-down, Bottom-up 방식의 점화식에 약간의 차이가 있습니다.

 

1. Top-down

"점화식 : D[i] = D[i+1] + D[i+2], D[i] = i번째 <부터> 해석될 수 있는 암호의 수"

예를 들어, 123456 이라는 암호가 있습니다.

이 암호를 해독할 때, 첫 번째 수는 1 / 23456 과 12 / 3456 이렇게 두 가지로 해석할 수 있습니다.

따라서, ["1"부터 해석될 수 있는 암호의 수 = "23456" 암호 해석 수 + "3456" 암호 해석 수] 라고 생각할 수 있습니다.

저는 아래와 같이 구현하였습니다.

 

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
#include <iostream>
#include <string>
#include <string.h>
#define MAX 5001
#define divider 1000000
 
using namespace std;
 
string code;
int cache[MAX];
int answer;
 
//알파벳으로 바뀔 수 있는지 확인
bool canChangeAlpha(string s) {
    int number = stoi(s);
 
    if (number >= 10 && number <= 26) {
        return true;
    }
    return false;
}
 
int countDecode(int index) {
 
    int& ret = cache[index];
 
    if (ret != -1) {
        return ret;
    }
 
    ret = 0;
 
    //현재 index부터 두 글자 떼어내기
    string cur = code.substr(index, 2);
 
    //첫 글자가 '0'인 경우 -> 암호 불가
    if (cur[0== '0') {
        return ret = 0;
    }
 
    //떼어낸 글자의 길이가 0 or 1인 경우 -> 종료 조건
    if (cur.length() == 0 || cur.length() == 1) {
        return ret = 1;
    }
 
    /*
    점화식 : D[i] = D[i+1] + D[i+2]
    ------------------------
    D[i] : i번째 <부터> 해석될 수 있는 암호의 수
    */
    ret += countDecode(index + 1);
    ret += canChangeAlpha(cur) ? countDecode(index + 2) : 0;
 
    return ret % divider;
}
 
int main() {
 
    //초기화
    code = "";
    memset(cache, -1sizeof(cache));
    answer = 0;
 
    //입력
    cin >> code;
 
    //해법
    answer = countDecode(0);
 
    //출력
    cout << answer << "\n";
 
    //종료
    return 0;
}
 

 

2. Bottou-up

"점화식 : D[i] = D[i-1] + D[i-2], D[i] = i번째 <까지> 해석될 수 있는 암호의 수"

위와 똑같이 123456 암호를 예시로 들겠습니다.

이 암호의 마지막 수는 12345 / 6 과 1234 / 56 이렇게 두 가지로 해석할 수 있습니다. (두 번째 경우는 사실 안됩니다.)

따라서, ["6"까지 해석될 수 있는 암호의 수 = "12345" 암호 해석 수 + "1234" 암호 해석 수] 라고 생각할 수 있습니다.

저는 아래와 같이 구현하였습니다.

 

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
#include <iostream>
#include <string>
#include <string.h>
#define MAX 5001
#define divider 1000000
 
using namespace std;
 
string code;
int cache[MAX];
int answer;
 
bool isZero(char c) {
    if (c == '0') {
        return true;
    }
    return false;
}
 
bool canChangeAlpha(string s) {
    int number = stoi(s);
 
    if (number >= 10 && number <= 26) {
        return true;
    }
    return false;
}
 
int countDecode(string s) {
 
    if (s[0== '0') {
        return 0;
    }
 
    /*
    점화식 : D[i] = D[i-1] + D[i-2]
    ------------------------
    D[i] : i번째 <까지> 해석될 수 있는 암호의 수
    */
    cache[0= cache[1= 1;
    for (int i = 2; i <= s.length(); i++) {
        cache[i] = isZero(s[i-1]) ? 0 : cache[i - 1];
        cache[i] += canChangeAlpha(s.substr(i - 22)) ? cache[i - 2] : 0;
        cache[i] %= divider;
    }
 
    return cache[s.length()];
}
 
int main() {
 
    //초기화
    code = "";
    memset(cache, -1sizeof(cache));
    answer = 0;
 
    //입력
    cin >> code;
 
    //해법
    answer = countDecode(code);
 
    //출력
    cout << answer << "\n";
 
    //종료
    return 0;
}
 

Top-down과 Bottom-up 사이의 접근 방식 차이가 느껴지셨으면 좋겠습니다.

 

사실 위 문제는 DP를 떠올리는 것보다 예외 처리를 하는 것이 더 까다롭게 느껴졌습니다. 저가 문제를 풀면서 겪었던 예외는

 

1. 숫자가 '0'으로 시작하는 경우

2. 숫자 두개로 해석할 때의 범위는, 10 이상 26 이하

3. 계산을 진행하면서 100,0000으로 나누어 주어야 함

 

이렇게 3가지 였습니다. 많은 도움이 되었으면 좋겠습니다.

 

DP에 대해 알아볼 수 있는 문제였습니다.

www.acmicpc.net/problem/2156

 

2156번: 포도주 시식

효주는 포도주 시식회에 갔다. 그 곳에 갔더니, 테이블 위에 다양한 포도주가 들어있는 포도주 잔이 일렬로 놓여 있었다. 효주는 포도주 시식을 하려고 하는데, 여기에는 다음과 같은 두 가지 규

www.acmicpc.net

<풀이>

1. 포도주들을 입력받는다.

2. 최대 2잔까지 연속으로 마시면서, 마실 수 있는 포도주의 최대량을 계산하고 출력한다.

 

<해법>

1. DP 점화식

=> 점화식은 "현재 포도주를 마시거나, 마시지 않는 모든 경우 중 최댓값" 입니다. 모든 경우는 아래 그림과 같이 나타낼 수 있습니다.

 

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
#include <iostream>
#include <string.h>
#include <algorithm>
#define MAX 10000 + 1
 
using namespace std;
 
int n;
int wine[MAX];
int cache[MAX];
int answer;
 
int main() {
 
    //초기화
    n = 0;
    memset(wine, 0sizeof(wine));
    memset(cache, 0sizeof(cache));
    answer = 0;
 
    //입력
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> wine[i];
    }
 
    //해법
    cache[1= wine[1];
    cache[2= wine[1+ wine[2];
    for (int i = 3; i <= n; i++) {
        int case1 = cache[i - 3+ wine[i - 1+ wine[i]; //~XOO 와인 마신 경우
        int case2 = cache[i - 2+ wine[i]; //~XO 와인 마신 경우
        int case3 = cache[i - 1]; //~X 와인 마신 경우
 
        cache[i] = max(max(case1, case2), case3);
    }
 
    //결과 갱신
    answer = cache[n];
 
    //출력
    cout << answer << "\n";
 
    //종료
    return 0;
}
 

 

DP에 대해 알아볼 수 있는 문제였습니다.

'알고리즘 문제풀이 > 백준' 카테고리의 다른 글

[C++] 백준 13460 - 구슬 탈출2  (1) 2022.12.21
[C++] 백준 2011 - 암호코드  (0) 2021.02.06
[C++] 백준 1074 - Z  (0) 2021.01.18
[C++] 백준 1780 - 종이의 개수  (0) 2021.01.14
[C++] 백준 2447 - 별 찍기 10  (0) 2021.01.14

www.acmicpc.net/problem/1074

 

1074번: Z

한수는 크기가 2N × 2N인 2차원 배열을 Z모양으로 탐색하려고 한다. 예를 들어, 2×2배열을 왼쪽 위칸, 오른쪽 위칸, 왼쪽 아래칸, 오른쪽 아래칸 순서대로 방문하면 Z모양이다. 만약, N > 1이 라서

www.acmicpc.net

<풀이>

1. N, r, c를 입력받는다.

2. 주어진 규칙에 따라 Z를 그려가면서, r행 c열에 방문한 번째를 구하고 출력한다.

 

<해법>

1. 방문한 번째를 구하는 방법

=> 저는 항상 문제를 풀 때, 프로그래밍 문제가 아니라고 생각하고 접근합니다. '내가 그냥 푼다면 어떻게 풀까?'

이 문제를 그냥 풀 때 빠르게 풀 수 있는 핵심은, 그려볼 필요 없는 Z는 그리지 않고 계산을 통해 구하는 것 입니다.

 

예를 들어서 설명해보겠습니다.

위와 같은 문제가 있을 때, 사람일 경우 어떻게 구할까요?

대부분의 사람이라면 빨간색 X부분은 Z를 그리지 않고, 빠르게 계산한 후에 (r, c)가 있는 부분을 탐색할 것입니다.

이제 문제를 모두 풀어보겠습니다.

이제 사람이 풀었던 방식을 컴퓨팅적인 방식으로 바꿔보겠습니다.

 

1. (r, c)가 해당 사각형에서 몇 분면에 있는지 파악한다.

2. (r, c)보다 이전에 있는 사분면들은 모두 빠르게 계산한다.

3. (r, c)가 있는 사분면을 탐색한다.

 

이렇게 바꿔볼 수 있고, 1~3번이 반복적으로 수행되므로 재귀함수로 구현하였습니다.

 

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
#include <iostream>
#include <math.h>
 
using namespace std;
 
int N, r, c;
int answer;
 
//(r,c)가 현재 사각형에서 몇사분면에 있는지 판단
int isInWhere(int x, int y, int n) {
 
    int position;
 
    //1사분면에 있을 경우
    if (r < x + n / 2 && c < y + n / 2) {
        position = 1;
    }
    //2사분면에 있을 경우
    else if (r < x + n / 2 && c < y + n) {
        position = 2;
    }
    //3사분면에 있을 경우
    else if (r < x + n && c < y + n / 2) {
        position = 3;
    }
    //4사분면에 있을 경우
    else {
        position = 4;
    }
 
    return position;
}
 
int makeZ(int x, int y, int n) {
 
    //정답 지점에 왔을 경우 -> 종료
    if (x == r && y == c) {
        return 0;
    }
 
    //한 변의 길이가 1인 경우 -> 종료
    if (n == 1) {
        return 1;
    }
    
    //해당 사각형에서 (r, c)가 몇사분면에 있는지 확인
    int position = isInWhere(x, y, n);
 
    //찾아야 하는 지점이 어디에 있는지에 따라서 더해야할 값이 다름
    if (position == 1) {
        return makeZ(x, y, n / 2);
    }
    else if (position == 2) {
        return (n / 2 * n / 2+ makeZ(x, y + n / 2, n / 2);
    }
    else if (position == 3) {
        return (n / 2 * n / 2* 2 + makeZ(x + n / 2, y, n / 2);
    }
    else {
        return (n / 2 * n / 2* 3 + makeZ(x + n / 2, y + n / 2, n / 2);
    }
}
 
int main() {
 
    //초기화
    N = 0, r = 0, c = 0;
    answer = 0;
 
    //입력
    cin >> N >> r >> c;
 
    //해법
    answer = makeZ(00, pow(2, N));
 
    //출력
    cout << answer << "\n";
 
    //종료
    return 0;
}
 

 

분할정복에 대해 알아볼 수 있는 문제였습니다.

www.acmicpc.net/problem/1780

 

1780번: 종이의 개수

N×N크기의 행렬로 표현되는 종이가 있다. 종이의 각 칸에는 -1, 0, 1의 세 값 중 하나가 저장되어 있다. 우리는 이 행렬을 적절한 크기로 자르려고 하는데, 이때 다음의 규칙에 따라 자르려고 한다.

www.acmicpc.net

<풀이>

1. 정사각형 종이의 한 변 크기인 N과 종이의 모양을 입력받는다.

2. 해당 종이를 지정한 방식으로 잘랐을 때, 각 종류별 종이의 개수를 출력한다.

 

<해법>

1. 자르는 방식을 분석하는 방법

=> 주어진 종이를 9부분으로 나누고 같은 방식으로 종이를 계속 잘라 나갑니다. 여기서 분할 정복을 사용해야 한다는 생각을 할 수 있습니다. 각 부분마다 같은 숫자가 쓰여져 있는지 확인하고, 아닌 경우 계속 잘라나가는 방식으로 구현하였습니다.

 

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
#include <iostream>
#include <string.h>
 
using namespace std;
 
int N;
int map[2200][2200];
//[0]:-1종이 개수, [1]:0종이 개수, [2]:1종이 개수
int answer[3];
 
//모두 같은 숫자인지 판단
bool allSameNumber(int x, int y, int n) {
    
    int check = map[x][y];
 
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (map[x + i][y + j] != check) {
                return false;
            }
        }
    }
 
    return true;
}
 
void makePaper(int x, int y, int n) {
 
    //종이가 모두 같은 숫자일 경우 -> 해당 종이 갯수 + 1, 종료
    if (allSameNumber(x, y, n)) {
        int paperNum = map[x][y];
        answer[paperNum + 1]++;
        return;
    }
 
    int div = n / 3;
 
    //9개로 나누어서 분할 정복
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            makePaper(x + div * i, y + div * j, div);
        }
    }
}
 
int main() {
 
    //초기화
    N = 0;
    memset(map, 0sizeof(map));
    memset(answer, 0sizeof(answer));
 
    //입력
    cin >> N;
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            cin >> map[i][j];
        }
    }
 
    //해법
    /*
    9개로 나누어서 분할 정복
    */
    makePaper(00, N);
 
    //출력
    for (int i = 0; i < 3; i++) {
        cout << answer[i] << "\n";
    }
 
    //종료
    return 0;
}
 

 

분할 정복에 대해 알아볼 수 있는 문제였습니다.

www.acmicpc.net/problem/2447

 

2447번: 별 찍기 - 10

재귀적인 패턴으로 별을 찍어 보자. N이 3의 거듭제곱(3, 9, 27, ...)이라고 할 때, 크기 N의 패턴은 N×N 정사각형 모양이다. 크기 3의 패턴은 가운데에 공백이 있고, 가운데를 제외한 모든 칸에 별이

www.acmicpc.net

<풀이>

1. 정사각형 한 변의 길이인 N을 입력받는다.

2. N x N 정사각형에 주어진 패턴대로 출력한다.

 

<해법>

1. 주어진 패턴 분석하는 방법

=> 주어진 패턴은 총 9부분으로 나누어서 생각할 수 있습니다. 나누어진 9부분을 또 더 탐색해야할 8부분과 탐색할 필요없는 가운데 부분으로 나눌 수 있습니다. 

 

2. N x N 정사각형 출력하는 방법

=> 분할 정복을 끝냈다면, 이제는 출력을 해야 합니다. 저는 N x N 정사각형 배열을 만들어서, 배열에 분할 정복된 패턴을 담아주는 방식으로 구현하였습니다.

 

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
#include <iostream>
#include <string.h>
 
using namespace std;
 
int N;
char map[2200][2200];
 
//x,y : 좌표, n : 한 변의 길이
void makeStar(int x, int y, int n) {
 
    //더이상 나눌 수 없는 지점
    if (n == 1) {
        map[x][y] = '*';
        return;
    }
 
    int div = n / 3;
 
    //9부분으로 나누기
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
 
            //가운데 지점은 빈 칸으로 채우기
            if (i % 3 == 1 && j % 3 == 1) {
 
                for (int k = 0; k < div; k++) {
                    for (int l = 0; l < div; l++) {
                        map[x + i + k][y + j + l] = ' ';
                    }
                }
 
                continue;
            }
 
            //나머지 8부분 탐색
            makeStar(x + div * i, y + div * j, div);
        }
    }
}
 
int main() {
 
    //초기화
    N = 0;
    memset(map, '#'sizeof(map));
 
    //입력
    cin >> N;
 
    //해법
    /*
    9등분으로 나누어서 분할 정복
    */
    makeStar(00, N);
 
    //출력
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            cout << map[i][j];
        }
        cout << "\n";
    }
 
    //종료
    return 0;
}
 

 

분할 정복과 구현에 대해 알아볼 수 있는 문제였습니다.

www.acmicpc.net/problem/12100

 

12100번: 2048 (Easy)

첫째 줄에 보드의 크기 N (1 ≤ N ≤ 20)이 주어진다. 둘째 줄부터 N개의 줄에는 게임판의 초기 상태가 주어진다. 0은 빈 칸을 나타내며, 이외의 값은 모두 블록을 나타낸다. 블록에 쓰여 있는 수는 2

www.acmicpc.net

<풀이>

1. 보드를 입력받는다.

2. 5번 움직일 수 있는 모든 경우에 따라, 가질 수 있는 최댓값들 중 최댓값을 구한다.

 

<해법>

1. 보드를 움직이는 방법

=> 블록은 2개씩만 합쳐질 수 있습니다. 따라서, 저는 비교하는 값과 비교당하는 값 이렇게 두 개를 나누어서 계산하였습니다. 그리고 만들어진 결과물을 큐에 옮겨 담고, 다시 그 큐의 값을 보드로 옮기는 방식으로 구현하였습니다.

 

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#include <iostream>
#include <string.h>
#include <queue>
#include <algorithm>
 
using namespace std;
 
int N;
int playMap[20][20];
int map[20][20];
int arr[5];
int answer;
 
//왼쪽으로 밀기
void swipeLeft() {
    for (int i = 0; i < N; i++) {
 
        //비교 대상(2개씩만 합쳐지므로)
        int compare = -1;
 
        //최종 줄
        queue<int> resLine;
 
        for (int j = 0; j < N; j++) {
 
            //빈 칸이 아니고,
            if (playMap[i][j] != 0) {
 
                //비교 대상이 없는 경우, 해당 지점이 비교 대상이 됨
                if (compare == -1) {
                    compare = playMap[i][j];
                }
                //비교 대상이 있을 경우,
                else {
                    //비교 대상과 해당 지점이 같은지 확인
                    if (playMap[i][j] == compare) {
 
                        //비교 대상과 같은 경우, 2배값 저장
                        resLine.push(compare * 2);
 
                        //비교 대상 초기화
                        compare = -1;
                    }
                    else {
 
                        //비교 대상과 다른 경우, 비교값 저장
                        resLine.push(compare);
 
                        //비교 대상은 다시 현 지점
                        compare = playMap[i][j];
                    }
                }
            }
        }
 
        //비교 대상이 아직 남아 있는 경우(끝 지점의 경우)
        if (compare != -1) {
            resLine.push(compare);
        }
 
        //줄 만들기
        for (int j = 0; j < N; j++) {
            if (!resLine.empty()) {
                playMap[i][j] = resLine.front();
                resLine.pop();
            }
            else {
                playMap[i][j] = 0;
            }
        }
    }
}
 
//오른쪽으로 밀기
void swipeRight() {
    for (int i = 0; i < N; i++) {
        int compare = -1;
        queue<int> resLine;
        for (int j = N - 1; j >= 0; j--) {
            if (playMap[i][j] != 0) {
                if (compare == -1) {
                    compare = playMap[i][j];
                }
                else {
                    if (playMap[i][j] == compare) {
                        resLine.push(compare * 2);
                        compare = -1;
                    }
                    else {
                        resLine.push(compare);
                        compare = playMap[i][j];
                    }
                }
            }
        }
        if (compare != -1) {
            resLine.push(compare);
        }
 
        for (int j = N - 1; j >= 0; j--) {
            if (!resLine.empty()) {
                int data = resLine.front();
                playMap[i][j] = data;
                resLine.pop();
            }
            else {
                playMap[i][j] = 0;
            }
        }
    }
}
 
//위쪽으로 밀기
void swipeUp() {
    for (int i = 0; i < N; i++) {
        int compare = -1;
        queue<int> resLine;
        for (int j = 0; j < N; j++) {
            if (playMap[j][i] != 0) {
                if (compare == -1) {
                    compare = playMap[j][i];
                }
                else {
                    if (playMap[j][i] == compare) {
                        resLine.push(compare * 2);
                        compare = -1;
                    }
                    else {
                        resLine.push(compare);
                        compare = playMap[j][i];
                    }
                }
            }
        }
        
        if (compare != -1) {
            resLine.push(compare);
        }
 
        for (int j = 0; j < N; j++) {
            if (!resLine.empty()) {
                playMap[j][i] = resLine.front();
                resLine.pop();
            }
            else {
                playMap[j][i] = 0;
            }
        }
    }
}
 
//아래쪽으로 밀기
void swipeDown() {
    for (int i = 0; i < N; i++) {
        int compare = -1;
        queue<int> resLine;
        for (int j = N - 1; j >= 0; j--) {
            if (playMap[j][i] != 0) {
                if (compare == -1) {
                    compare = playMap[j][i];
                }
                else {
                    if (playMap[j][i] == compare) {
                        resLine.push(compare * 2);
                        compare = -1;
                    }
                    else {
                        resLine.push(compare);
                        compare = playMap[j][i];
                    }
                }
            }
        }
 
        if (compare != -1) {
            resLine.push(compare);
        }
 
        for (int j = N - 1; j >= 0; j--) {
            if (!resLine.empty()) {
                playMap[j][i] = resLine.front();
                resLine.pop();
            }
            else {
                playMap[j][i] = 0;
            }
        }
    }
}
 
int findMaxFromPlayMap() {
    int res = -1;
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            if (playMap[i][j] > res) {
                res = playMap[i][j];
            }
        }
    }
    return res;
}
 
void play(int playCount) {
 
    //종료조건 : 5번 움직였을 경우
    if (playCount == 5) {
 
        //playMap에 입력받은 map 복사
        memcpy(playMap, map, sizeof(playMap));
 
        //방향에 맞게 움직이기
        for (int i = 0; i < 5; i++) {
            switch (arr[i]) {
            case 0:
                swipeRight();
                break;
            case 1:
                swipeLeft();
                break;
            case 2:
                swipeUp();
                break;
            case 3:
                swipeDown();
                break;
            }
        }
 
        //결과 갱신
        answer = max(findMaxFromPlayMap(), answer);
 
        //종료
        return;
    }
 
    //4개 방향 모두 해보기
    for (int i = 0; i < 4; i++) {
        arr[playCount] = i;
        play(playCount + 1);
    }
}
 
int main() {
 
    //초기화
    answer = 0;
 
    //입력
    cin >> N;
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            cin >> map[i][j];
        }
    }
 
    //해법
    /*
    위, 아래, 오른쪽, 왼쪽을 5번 움직일 수 있는 경우를 모두 구하고
    직접 움직인후, 최대값을 갱신한다.
    */
    play(0);
 
    //출력
    cout << answer << "\n";
 
    //종료
    return 0;
}
 

 

구현, 시뮬레이션, 브루트포스에 대해 알아볼 수 있는 문제였습니다.

www.acmicpc.net/problem/1018

 

1018번: 체스판 다시 칠하기

첫째 줄에 N과 M이 주어진다. N과 M은 8보다 크거나 같고, 50보다 작거나 같은 자연수이다. 둘째 줄부터 N개의 줄에는 보드의 각 행의 상태가 주어진다. B는 검은색이며, W는 흰색이다.

www.acmicpc.net

<풀이>

1. 보드를 입력받는다.

2. 해당 보드에서 8x8 사이즈로 만들 수 있는 체스판 중, 색칠해야하는 최소 개수를 출력한다.

 

<해법>

모든 경우를 다 해보아야 합니다. 8x8 사이즈의 판을 탐색하면서, 해당 판에서 B 또는 W로 모두 칠해보고 최소값을 갱신합니다.

 

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
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
 
using namespace std;
 
int N, M;
vector<string> map;
int answer;
 
//왼쪽 위가 B인 체스판
string Bchess[] = {
    "BWBWBWBW",
    "WBWBWBWB",
    "BWBWBWBW",
    "WBWBWBWB",
    "BWBWBWBW",
    "WBWBWBWB",
    "BWBWBWBW",
    "WBWBWBWB"
};
 
//왼쪽 위가 W인 체스판
string Wchess[] = {
    "WBWBWBWB",
    "BWBWBWBW",
    "WBWBWBWB",
    "BWBWBWBW",
    "WBWBWBWB",
    "BWBWBWBW",
    "WBWBWBWB",
    "BWBWBWBW"
};
 
//해당 좌표에서 칠하는 페인트의 최소 개수
int minPaintCount(int x, int y) {
 
    //왼쪽 위가 B인 체스판으로 만들 경우
    int BpaintCount = 0;
    for (int i = 0; i < 8; i++) {
        for (int j = 0; j < 8; j++) {
            if (map[i + x][j + y] != Bchess[i][j]) {
                BpaintCount++;
            }
        }
    }
 
    //왼쪽 위가 W인 체스판으로 만들 경우
    int WpaintCount = 0;
    for (int i = 0; i < 8; i++) {
        for (int j = 0; j < 8; j++) {
            if (map[i + x][j + y] != Wchess[i][j]) {
                WpaintCount++;
            }
        }
    }
 
    //최소값 반환
    return min(BpaintCount, WpaintCount);
}
 
int main() {
 
    //초기화
    answer = -1;
 
    //입력
    cin >> N >> M;
    for (int i = 0; i < N; i++) {
        string tmp;
        cin >> tmp;
        map.push_back(tmp);
    }
 
    //해법
    for (int i = 0; i <= N - 8; i++) {
        for (int j = 0; j <= M - 8; j++) {
 
            int minPaint = minPaintCount(i, j);
            
            //결과 갱신
            if (answer == -1 || minPaint < answer) {
                answer = minPaint;
            }
        }
    }
 
    //출력
    cout << answer;
 
    //종료
    return 0;
}
 

 

구현과 브루트포스에 대해 알아볼 수 있는 문제였습니다.

'알고리즘 문제풀이 > 백준' 카테고리의 다른 글

[C++] 백준 2447 - 별 찍기 10  (0) 2021.01.14
[C++] 백준 12100 - 2048(Easy)  (0) 2021.01.10
[C++] 백준 14502 - 연구소  (0) 2021.01.03
[C++] 백준 1120 - 문자열  (0) 2021.01.03
[C++] 백준 4949 - 균형잡힌 세상  (0) 2021.01.01

www.acmicpc.net/problem/14502

 

14502번: 연구소

인체에 치명적인 바이러스를 연구하던 연구소에서 바이러스가 유출되었다. 다행히 바이러스는 아직 퍼지지 않았고, 바이러스의 확산을 막기 위해서 연구소에 벽을 세우려고 한다. 연구소는 크

www.acmicpc.net

<풀이>

1. 연구소 지도를 입력받는다.

2. 연구소에 벽 3개를 세우고, 바이러스가 퍼졌을 때 안전 영역을 구한다.

3. 안전 영역의 최댓값을 출력한다.

 

<해법>

1. 연구소에 벽 3개를 세우는 방법.

=> 벽 3개를 어떻게 세워야 하는지 정할 수 있는 최적의 방법이 없습니다. 따라서, 가능한 곳에 벽 3개를 모두 세워보아야 합니다. 저는 벽을 세울 수 있는 좌표들을 담아 놓고, 하나씩 세우는 방법으로 진행하였습니다. 기본적인 브루트포스 진행방식을 이해하고 있어야 합니다.

 

2. 안전 영역을 구하는 방법.

=> 벽 3개를 모두 세웠으면 이제 바이러스를 퍼트려야 합니다. 바이러스는 BFS를 이용하여 퍼트렸습니다. 마지막으로, 안전 영역을 구하는 방법은 바이러스가 다 퍼진 후, 남은 영역의 개수를 세는 방식으로 진행하였습니다.

 

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#include <iostream>
#include <cstring>
#include <queue>
 
using namespace std;
 
//구조체 : 좌표
struct pos {
    int x, y;
};
 
//4방향 : 북,동,남,서
int di[] = { -1,0,1,0 };
int dj[] = { 0,1,0,-1 };
 
int N, M;
int map[10][10];
vector<pos> space;
vector<pos> virus;
int copyMap[10][10];
int answer;
 
void spread_virus_to_copyMap() {
 
    queue<pos> q;
    
    //큐에 바이러스 모두 담기
    for (int i = 0; i < virus.size(); i++) {
        q.push(virus[i]);
    }
 
    //BFS 탐색
    while (!q.empty()) {
        pos cur = q.front();
        q.pop();
 
        for (int i = 0; i < 4; i++) {
            pos nxt = cur;
            nxt.x += di[i];
            nxt.y += dj[i];
 
            if (nxt.x < 0 || nxt.y < 0 || nxt.x >= N || nxt.y >= M) {
                continue;
            }
 
            if (copyMap[nxt.x][nxt.y] == 0) {
                copyMap[nxt.x][nxt.y] = 2;
                q.push(nxt);
            }
        }
    }
}
 
int count_safeSpace_from_copyMap() {
 
    int safeSpaceCnt = 0;
 
    //안전 영역(=0) 개수 세기
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            if (copyMap[i][j] == 0) {
                safeSpaceCnt++;
            }
        }
    }
 
    //안전 영역 반환
    return safeSpaceCnt;
}
 
void makeWall(int spaceIdx, int wallCnt) {
 
    //종료조건 : 벽을 3개 모두 세웠을 경우
    if (wallCnt == 3) {
 
        //시뮬레이션을 하기 위해 맵 복사
        memcpy(copyMap, map, sizeof(copyMap));
 
        //복사된 맵에 바이러스 퍼트리기(BFS)
        spread_virus_to_copyMap();
 
        //안전 영역 구하기
        int safeSpaceCnt = count_safeSpace_from_copyMap();
 
        //결과 갱신
        if (safeSpaceCnt > answer) {
            answer = safeSpaceCnt;
        }
 
        //종료
        return;
    }
 
    //빈 공간에 벽 하나씩 세우기
    for (int i = spaceIdx; i < space.size(); i++) {
        
        /*
        빈 공간 좌표 꺼내서, 벽을 세우고 탐색한 후 다시 벽 없애기
        */
 
        int x = space[i].x;
        int y = space[i].y;
 
        map[x][y] = 1;
        makeWall(i + 1, wallCnt + 1);
        map[x][y] = 0;
    }
}
 
int main() {
 
    //초기화
    answer = -1;
    
    //입력
    cin >> N >> M;
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            cin >> map[i][j];
 
            //빈 공간(=벽 세울 위치) 저장
            if (map[i][j] == 0) {
                space.push_back({ i,j });
            }
            //바이러스 위치 저장
            else if (map[i][j] == 2) {
                virus.push_back({ i,j });
            }
        }
    }
 
    //벽 세우기(브루트포스)
    makeWall(00);
 
    //출력
    cout << answer;
 
    //종료
    return 0;
}
 

 

브루트포스와 BFS에 대해 알아볼 수 있는 문제였습니다.

+ Recent posts