백준 17136번 색종이 붙이기

#17136. 색종이 붙이기

Problem

Solution

  • DFS + BackTracking을 사용하여 푼다.
    (처음에 BFS+Greedy를 이용해 풀었다가 틀렸다…반례가 존재)
  • 10 x 10 크기의 모든 배열에서 모든 지점을 탐색한다.
    • 탐색 횟수가 100이라면 모든 지점을 탐색한 경우라 값을 갱신하고 종료한다.
    • 행과 열은 0 → 100개의 원소들에 맞게 계산
    • 이미 ans 가 지금까지 구한 개수(cnt) 보다 크면 종료 (BackTracking)
    • 해당 지점이 1이면 색종이 크기가 큰 5부터 검사 시작
      • 해당 크기가 가능하면 붙이고 다음 경우 탐색
        탐색이 끝난 경우 원래대로 돌려놓아야 모든 경우 탐색이 가능하다.
    • 해당 지점이 0이면 다음 번째 원소 탐색

1 Try

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
#include <iostream>
using namespace std;
int ans = 100;
int map[10][10];
int type[5] = {5, 5, 5, 5, 5 }; // 1 2 3 4 5 종류
void Update(int x, int y, int size, int status) {
for (int i = x; i < x + size; ++i) {
for (int j = y; j < y + size; ++j) {
map[i][j] = status;
}
}
}
bool Check(int x, int y, int size) {
if (x + size > 10 || y + size > 10) return false; // 범위 밖
for (int i = x; i < x + size; ++i) {
for (int j = y; j < y + size; ++j) {
if (map[i][j] == 0) return false;
}
}
return true;
}
void DFS(int n, int cnt) {
if (n == 100) { // 모든 경우를 탐색한 경우 (총 100개)
if (ans > cnt) ans = cnt;
return;
}
int x = n / 10;
int y = n % 10;
if (ans <= cnt) return; // BackTracking

if (map[x][y] == 1) {
for (int i = 5; i > 0; --i) { // 크기 5부터 시작
if (type[i-1] > 0) {
if (Check(x, y, i)) {
type[i - 1]--;
Update(x, y, i, 0); // 색종이 붙이기
DFS(n+1, cnt + 1);
Update(x, y, i, 1); // 색종이 다시 때기
type[i - 1]++;
}
}
}
}
else DFS(n + 1, cnt);
}
int main() {
for (int i = 0; i < 10; ++i) {
for (int j = 0; j < 10; ++j) {
cin >> map[i][j];
}
}
DFS(0, 0);
if (ans == 100) ans = -1;
cout << ans << "\n";
return 0;
}

백준 17135번 캐슬 디펜스

#17135. 캐슬 디펜스

Problem

Solution

  1. 궁수 3명의 위치를 지정한다. (중복x, 오름차순 순열조합)
  2. 적이 사라질 때까지(=궁수가 0번째 행으로 이동할 때까지) 게임을 진행한다.
    1. 궁수는 가장 가까운 위치의 적을 공격해야 하며, 여럿이면 왼쪽의 적부터 공격한다. 또한 동시에 공격이 가능하다(=위의 조건을 만족하는 같은 적을 공격한다.)
    2. 공격이 끝나면 배열을 바꾸지 않고 궁수의 위치를 이동시킨다. (위로)
      step 변수를 두어 범위를 좁혀간다.

가까운 적을 먼저 찾아 공격하기 위해 BFS 탐색을 사용한다.

  • 각 궁수별로 queue 를 만들어 자신의 위치를 저장하고 탐색 시작 (최대 D까지)
  • 궁수가 적을 공격할 경우 다음 궁수에게 차례를 넘긴다. (즉, 공격하면 자신의 턴 끝=탐색 끝)
  • check array를 사용해 공격한 적일 경우 해당 step + 1의 값을 준다. (step이 0부터 시작, check는 0으로 초기화 되어있기에)
  • step을 이용해 check에 값을 준 이유는 다음 step에서 궁수의 위치가 이동될 때 이전 턴에서 공격 당해 제외된 적인지 아닌지를 판단하기 위함이다. (step의 값이 변하니까 중복되지 않게 탐색 가능)

1 Try

  • 궁수는 가장 가까운 위치의 적을 공격, 가까운 적이 여럿이면 왼쪽 우선 그리고 동시에 공격 가능하다는 조건을 충족하지 못 했다. → 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
    #include <iostream>
    #include <cstring>
    #include <cmath>
    using namespace std;
    int N, M, D, ans;
    int map[16][16];
    bool check[16][16];
    int archer[3] = { -1, -1, -1 };
    int getDistance(int a_x, int a_y, int b_x, int b_y) {
    return abs(a_x - b_x) + abs(a_y - b_y);
    }
    void Game() {
    int step = 0, res = 0;
    memset(check, 0, sizeof(check));
    while (true) {
    int attack_cnt = 0;
    for (int i = N - step - 1; i >= (N -step) - D && i >= 0; --i) {
    for (int k = attack_cnt; k < 3; ++k) { // 3명의 궁수
    for (int j = 0; j < M; ++j) {
    if (D >= getDistance(i, j, N - step, archer[k])
    && map[i][j] == 1 && !check[i][j]) { // 공격 개시
    res++;
    check[i][j] = true;
    attack_cnt++;
    break; // 한 번만 공격 가능
    }
    }
    }
    }
    step++;
    if (N - step - 1 < 0) break;
    }
    if (ans < res) ans = res;
    }
    void SelectPosition(int n, int cnt) {
    if (cnt == 3) {
    Game();
    return;
    }
    for (int i = n; i < M; ++i) {
    if (archer[cnt] != -1) continue;
    archer[cnt] = i;
    SelectPosition(i + 1, cnt + 1);
    archer[cnt] = -1;
    }
    }
    int main() {
    cin >> N >> M >> D;
    for (int i = 0; i < N; ++i) {
    for (int j = 0; j < M; ++j) {
    cin >> map[i][j];
    }
    }
    SelectPosition(0, 0);
    cout << ans << "\n";
    return 0;
    }

    2 Try

    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
    #include <iostream>
    #include <cstring>
    #include <queue>
    using namespace std;
    int N, M, D, ans;
    int map[16][16];
    int check[16][16];
    int archer[3] = { -1, -1, -1 };
    int dx[3] = { 0, -1, 0 };
    int dy[3] = { -1, 0, 1 };
    void Game() {
    int step = 0, res = 0; // step은 1턴이 끝날 때 이동을 위함
    memset(check, 0, sizeof(check));
    while (true) {
    for (int i = 0; i < 3; ++i) { // 궁수 각 3명에 대하여
    queue<pair<int, int> > q;
    q.push({ N - step, archer[i] }); // 궁수 현재 위치부터 시작
    for(int j = 0; j < D; ++j) { // 최대 D까지만큼만 탐색
    int len = q.size();
    bool end_flag = false; // 적을 공격했을 때 종료 flag
    for (int k = 0; k < len; ++k) {
    int x = q.front().first;
    int y = q.front().second;
    q.pop();
    for (int dir = 0; dir < 3; ++dir) { // 왼 위 오 순서로 탐색 시작
    int d_x = x + dx[dir];
    int d_y = y + dy[dir];
    if (d_x > -1 && d_y > -1 && d_x < N - step && d_y < M) { // N - step은 궁수 위치
    if (d_x < N - step - D) continue; // [N - step - D, N-step) 탐색 범위
    if (map[d_x][d_y] == 1 && check[d_x][d_y] == 0) { // 처음 적을 공격한 경우
    res++;
    check[d_x][d_y] = step + 1; // step을 옮겼을 때 중복 방지를 위함
    end_flag = true;
    break;
    }
    else if (map[d_x][d_y] == 1 && check[d_x][d_y] == step + 1) { // 동시에 적을 공격한 경우
    end_flag = true;
    break;
    }
    else { // 적을 발견하지 못 한 경우
    q.push({ d_x, d_y });
    }
    }
    }
    if (end_flag) break;
    }
    if (end_flag) break;
    }
    }
    step++;
    if (N - step - 1 < 0) break;
    }
    if (ans < res) ans = res;
    }
    void SelectPosition(int n, int cnt) {
    if (cnt == 3) {
    Game();
    return;
    }
    for (int i = n; i < M; ++i) {
    if (archer[cnt] != -1) continue;
    archer[cnt] = i;
    SelectPosition(i + 1, cnt + 1);
    archer[cnt] = -1;
    }
    }
    int main() {
    cin >> N >> M >> D;
    for (int i = 0; i < N; ++i) {
    for (int j = 0; j < M; ++j) {
    cin >> map[i][j];
    }
    }
    SelectPosition(0, 0);
    cout << ans << "\n";
    return 0;
    }

백준 17070번 파이프 옮기기 1

#17070. 파이프 옮기기 1

문제링크

Problem

  • 파이프는 2개의 연속된 칸을 차지
  • 회전이 가능 (3가지: 수평선, 수직선, 대각선(4칸 차지))

Goal: (0, 0) 과 (0, 1)에 위치한 하나의 파이프를 (N, N)으로 옮기는 방법의 개수
불가능한 경우 0

  • 파이프 옮기기

오른쪽, 아래, 오른쪽 아래 대각선 방향으로 옮길 수 있음
옮길 때 45도 회전 가능
벽에 닿으면 안됨

  • 가로(수평) → 오른쪽 or 오른쪽 아래 대각선
  • 세로(수직) → 아래 or 오른쪽 아래 대각선
  • 오른쪽 아래 대각선 → 3가지 모두 가능

입력

N: 3~16
빈칸 0 벽은 1
// 예제
0 0 1 0 
0 0 0 0 
0 0 0 0
0 0 0 0
-> 0

맨 처음 파이프는 (0,0), (0,1) 즉 가로이기에 가능한 방향인
오른쪽, 오른쪽 아래 대각선으로 옮길 수 없다.

Solution

  • 현재 type 정보와 위치를 알고 있어야 한다.
  • 현재 type에 따라 옮길 수 있는 방향이 다르다.
  • 현재 type에 따라 옮길 수 있는지 check를 해주어야 한다.
  • check
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    가로: 오른쪽 1칸 검사
    세로: 아래 1칸 검사
    대각선: 오른쪽 1칸, 아래 1칸, 오른쪽 아래 1칸 검사
    // # 1.
    가로 -> 가로 (자기 자신)
    -> 대각선
    // # 2.
    세로 -> 세로 (자기 자신)
    -> 대각선
    // # 3.
    대각선 -> 세로
    -> 가로
    -> 대각선
  1. 모두 자기 자신의 타입 그대로 옮길 수 있다.
  2. 대각선을 가기 위해서는 가로, 세로로 갈 수 있는 조건 역시 만족해야 한다.

위를 이용하여 탐색 코드를 구현한다.

1
2
3
4
5
6
7
8
void 탐색(현재 위치, 현재 타입)
도착했을 때 answer++후 리턴
for(3번)
check할 위치(오른쪽, 아래, 오른쪽 아래)
경계확인
(벽이 아니고 자신과 같은 type)이거나(벽이 아니고 대각선)
탐색(옮길 위치, 가능한 타입)
모든 곳을 검사했을 때만 대각선 탐색

1 Try

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
#include <cstdio>
#define MAX 16
using namespace std;
int n, answer;
int map[MAX][MAX];
bool check[MAX][MAX];
// 오른쪽 1칸 검사, 아래 1칸 검사, 오른쪽 아래 1칸 검사
int dx[3] = { 0, 1, 1 };
int dy[3] = { 1, 0, 1 };
bool isBound(int r, int c) {
if (r > -1 && c > -1 && r < n && c < n) return true;
return false;
}
// type 0 : 가로, 1 : 세로, 2 : 대각선
void dfs(int r, int c, int type) {
if (r == n - 1 && c == n - 1) {
answer++;
return;
}
// 현재 타입이 가로일 때 어차피 대각선 때문에 3칸 다 검사해야 한다.
int x, y;
bool flag = true;
for (int i = 0; i < 3; ++i) {
x = r + dx[i];
y = c + dy[i];
if (isBound(x, y)) {
// 같은 type일 경우 or 대각선일 경우, i==2이면 대각선 경우가 한 번 더 구해지는 꼴이다.
if ((map[x][y] == 0 && type == i || map[x][y] == 0 && type == 2) && i != 2) {
dfs(x, y, i);
}
else if (map[x][y] != 0) flag = false;
}
else flag = false;
}
if (flag) {
dfs(x, y, 2); // 오른쪽 아래 대각선
}
}
int main() {
scanf("%d", &n);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
scanf("%d", &map[i][j]);
}
}
dfs(0, 1, 0);
printf("%d\n", answer);
return 0;
}

백준 16637번 괄호 추가하기

#16637. 괄호 추가하기

Problem

Solution

  1. 어느 연산자에 괄호를 넣을지 정해야 한다.
    괄호는 연산자 연속으로 넣을 수 없다. (1+(3)-2) → 말도 안되는 식
    그렇기에 이전 괄호의 위치를 알고 있어야 한다.
    괄호는 첫 번째에 넣을 필요는 없으며(op[i]≠1)
    이전 괄호의 위치와 2 차이가 나면 안된다.
  2. 괄호를 모두 지정했으면 각 경우에 맞게 식을 계산한다.
  • 괄호가 등장하면 한 번에 계산하고 반환한다.
  • Stack을 사용하여 연산자만 넣는다.
  • Stack이 비어있지 않으면 꺼내어 연산자에 맞게 계산한다.

1 Try

  • *(곱셈)은 괄호가 필요 없을 것이라 생각하여 괄호를 넣어주지 않았다.
    하지만 연산은 왼쪽에서부터 순서대로 진행되기에 곱셈도 괄호가 필요하다.

    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 <stack>
    using namespace std;
    char expr[20];
    bool selected[20];
    int op[10];
    int N, ans, total_bracket;
    int TaskBracket(int start) {
    int a = expr[start] - '0';
    int b = expr[start+2] - '0';
    char opr = expr[start + 1];
    if (opr == '+') {
    return a + b;
    }
    else {
    return a - b;
    }
    }
    void Calculate() {
    int res = expr[0]-'0', tmp = 0, idx = 0;
    bool flag = false;
    stack<char> st;
    for (int i = 1; i < N; ++i) {
    if (i != N-1 && selected[i+1]) {
    tmp = TaskBracket(i);
    i+=2;
    flag = true;
    }
    if (!st.empty()) {
    if (st.top() == '+') {
    if(flag) res += tmp;
    else res += int(expr[i] - '0');
    }
    else if (st.top() == '-') {
    if(flag) res -= tmp;
    else res -= int(expr[i] - '0');
    }
    else {
    if(flag) res *= tmp;
    else res *= int(expr[i] - '0');
    }
    st.pop();
    flag = false;
    }
    if (expr[i] == '+' || expr[i] == '-' || expr[i] == '*') {
    st.push(expr[i]);
    }
    }
    if (res > ans) ans = res;
    }
    void SelectBracket(int n, int cnt, int prior) {
    if (cnt <= total_bracket) {
    Calculate();
    }
    for (int i = n; i < total_bracket; ++i) {
    if (selected[op[i]] || op[i] == 1) continue;
    if (op[i] - prior == 2) continue;
    selected[op[i]] = true;
    SelectBracket(i+1, cnt+1, op[i]);
    selected[op[i]] = false;
    }
    }
    int main() {
    cin >> N;
    for (int i = 0; i < N; ++i) {
    cin >> expr[i];
    if (expr[i] == '+' || expr[i] == '-') {
    op[total_bracket++] = i;
    }
    }
    SelectBracket(0, 0, 0);
    cout << ans << "\n";
    return 0;
    }

    2 Try

  • ans(정답)의 초기값을 0으로 설정해서 틀렸다. 정답은 (-2^31, 2^31)의 범위에 속한다.

    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 <stack>
    using namespace std;
    char expr[20];
    bool selected[20];
    int op[10];
    int N, total_bracket, ans;
    int TaskBracket(int start) {
    int a = expr[start] - '0';
    int b = expr[start+2] - '0';
    char opr = expr[start + 1];
    if (opr == '+') {
    return a + b;
    }
    else if(opr == '-') {
    return a - b;
    }
    else return a * b;
    }
    void Calculate() {
    int res = expr[0]-'0', tmp = 0, idx = 0;
    bool flag = false;
    stack<char> st;
    for (int i = 1; i < N; ++i) {
    if (i != N-1 && selected[i+1]) {
    tmp = TaskBracket(i);
    i+=2;
    flag = true;
    }
    if (!st.empty()) {
    if (st.top() == '+') {
    if(flag) res += tmp;
    else res += int(expr[i] - '0');
    }
    else if (st.top() == '-') {
    if(flag) res -= tmp;
    else res -= int(expr[i] - '0');
    }
    else {
    if(flag) res *= tmp;
    else res *= int(expr[i] - '0');
    }
    st.pop();
    flag = false;
    }
    if (expr[i] == '+' || expr[i] == '-' || expr[i] == '*') {
    st.push(expr[i]);
    }
    }
    if (res > ans) ans = res;
    }
    void SelectBracket(int n, int cnt, int prior) {
    if (cnt <= total_bracket) {
    Calculate();
    }
    for (int i = n; i < total_bracket; ++i) {
    if (selected[op[i]] || op[i] == 1) continue;
    if (op[i] - prior == 2) continue;
    selected[op[i]] = true;
    SelectBracket(i+1, cnt+1, op[i]);
    selected[op[i]] = false;
    }
    }
    int main() {
    cin >> N;
    for (int i = 0; i < N; ++i) {
    cin >> expr[i];
    if (expr[i] == '+' || expr[i] == '-' || expr[i] == '*') {
    op[total_bracket++] = i;
    }
    }
    SelectBracket(0, 0, 0);
    cout << ans << "\n";
    return 0;
    }

    3 Try

    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
    #include <iostream>
    #include <limits>
    #include <stack>
    using namespace std;
    char expr[20];
    bool selected[20];
    int op[10];
    int N, total_bracket, ans = numeric_limits<int>::min();
    int TaskBracket(int start) {
    int a = expr[start] - '0';
    int b = expr[start+2] - '0';
    char opr = expr[start + 1];
    if (opr == '+') {
    return a + b;
    }
    else if(opr == '-') {
    return a - b;
    }
    else return a * b;
    }
    void Calculate() {
    int res = expr[0]-'0', tmp = 0, idx = 0;
    bool flag = false;
    stack<char> st;
    for (int i = 1; i < N; ++i) {
    if (i != N-1 && selected[i+1]) { // 괄호인 경우
    tmp = TaskBracket(i); // 한 번에 계산
    i+=2;
    flag = true;
    }
    if (!st.empty()) { // 스택에 연산자가 있을 경우
    if (st.top() == '+') {
    if(flag) res += tmp;
    else res += int(expr[i] - '0');
    }
    else if (st.top() == '-') {
    if(flag) res -= tmp;
    else res -= int(expr[i] - '0');
    }
    else {
    if(flag) res *= tmp;
    else res *= int(expr[i] - '0');
    }
    st.pop();
    flag = false;
    }
    if (expr[i] == '+' || expr[i] == '-' || expr[i] == '*') {
    st.push(expr[i]);
    }
    }
    if (res > ans) ans = res;
    }
    void SelectBracket(int n, int cnt, int prior) {
    if (cnt <= total_bracket) {
    Calculate();
    }
    for (int i = n; i < total_bracket; ++i) {
    if (selected[op[i]] || op[i] == 1) continue; // 첫 번째 연산자에 괄호는 필요 없다.
    if (op[i] - prior == 2) continue; // 연속적인 괄호는 불가능
    selected[op[i]] = true;
    SelectBracket(i+1, cnt+1, op[i]);
    selected[op[i]] = false;
    }
    }
    int main() {
    cin >> N;
    for (int i = 0; i < N; ++i) {
    cin >> expr[i];
    if (expr[i] == '+' || expr[i] == '-' || expr[i] == '*') {
    op[total_bracket++] = i;
    }
    }
    SelectBracket(0, 0, 0);
    cout << ans << "\n";
    return 0;
    }

백준 16236번 아기 상어

#16236. 아기 상어

문제링크

Problem

  • N x N 공간
  • 물고기 M, 아기 상어 1마리 (1칸에 최대 1마리)
  • 아기 상어 처음 크기 : 2, 물고기도 크기 존재
  • 아기 상어 움직임 (1초마다 상하좌우)
    자신보다 큰 물고기 있는 칸은 못 감 나머진 이동 가능
    자신보다 작은 물고기만 먹을 수 있고, 크기가 같다면 지나가는건 가능
  • 자신의 크기만큼 물고기를 먹어야 크기가 1 증가한다.
  • 상어가 이동할 곳 결정 방법
    • 더 이상 먹을 물고기가 없으면 종료
    • 먹을 수 있는 물고기가 1마리라면, 그 물고기를 먹는다. (여기부터 이동)
    • 1마리보다 많다면, 거리가 가장 가까운 물고기부터 먹는다.
      • 가장 가까운 물고기가 여럿이면 가장 위
      • 그런 물고기가 또 여럿이면 가장 왼쪽에 있는 물고기를 먹는다.

Goal: 종료되는 시간은?

  • 입력

    0: 빈 칸
    1~6: 물고기 크기
    9: 아기 상어

Solution

  • 입력값 다루기

    struct shark {

      int r, c, size, cnt= 0;
    

    }baby;

아기 상어의 위치, 크기, 먹은 수를 저장할 수 있도록 구조체를 만든다.

  • 다음 작업이 반복된다.
  1. 먹이를 찾기
  2. 찾았다면 그 중 규칙에 따라 먹이 선택하기
  • 먹이 찾기
  1. 탐색할 queue와 먹을 수 있는 물고기를 저장할 queue가 필요하다.
  2. 탐색 지점의 표시를 위해 check 배열이 필요하다.
  3. 아기 상어가 이동한 후 다음 먹이까지 찾아가는데 걸리는 시간을 따로 저장해야 한다. (part_time)
  4. 탐색할 지점은 경계를 넘지 않고 아기 상어 크기보다 작거나 같고 탐색표시가 안된 지점이다.
    • 탐색이 가능할 때
    1. 탐색 표시를 하고 탐색 queue에 넣어준다.
    2. 아기 상어 크기보다 작은 물고기라면 먹이 queue에 넣어준다.
  5. 1초간 갈 수 있는 탐색이 끝나면 먹이 queue가 비어있는지를 판단한다.
    1. 먹이 queue에 먹이가 들어있으면 걸린 시간(part_time)을 더해준다.
    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
      void findPrey(int row, int col) {
      queue<pair<int, int>> q, prey;
      q.push({ row, col });
      memset(check, 0, sizeof(check));
      check[row][col] = true;
      int part_time = 0;
      while (!q.empty()) { // 순회할 때마다 1초 증가
      part_time++;
      int len = q.size();
      for (int i = 0; i < len; ++i) {
      int r = q.front().first;
      int c = q.front().second;
      q.pop();
      for (int j = 0; j < 4; ++j) {
      int x = r + dx[j];
      int y = c + dy[j];
      if (isBound(x, y) && baby.size >= map[x][y] && !check[x][y]) {
      if (baby.size > map[x][y] && map[x][y] > 0) {
      prey.push({ x, y });
      }
      check[x][y] = true;
      q.push({ x, y });
      }
      }
      }
      if (!prey.empty()) {
      selectPrey(prey);
      time += part_time;
      return;
      }
      }
      isEnd = true;
      }
      만약 탐색을 모두 했는데도 먹이가 없다면 isEndtrue가 되므로 작업이 종료된다.
  • 규칙에 따라 먹이 선택하기
    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
    void selectPrey(queue<pair<int, int>> &prey) {
    int prior_x = n, prior_y = n;
    while (!prey.empty()) {
    int x = prey.front().first;
    int y = prey.front().second;
    prey.pop();
    if (x < prior_x) {
    prior_x = x;
    prior_y = y;
    }
    else if (x == prior_x) {
    if (y < prior_y) {
    prior_y = y;
    }
    }
    }
    map[baby.r][baby.c] = 0;
    map[prior_x][prior_y] = 9;
    baby.r = prior_x;
    baby.c = prior_y;
    baby.cnt++;
    if (baby.cnt == baby.size) {
    baby.size++;
    baby.cnt = 0;
    }
    }
    이 먹이 queue에 들어있는 (여럿이라면)물고기들은 모두 같은 거리이기에 문제의 규칙인 가장 위쪽 우선, 가장 왼쪽 우선을 따라 물고기를 선택한다.

먹을 물고기가 선택되면 아기 상어의 위치를 변경해준다. 크기 또한 먹은 수에 맞게 변경해준다.

1 Try

  • 시간초과 코드
    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
    #include <cstdio>
    #include <cstring>
    #include <queue>
    #define MAX 21
    using namespace std;
    int n, time;
    bool isEnd;
    int map[MAX][MAX];
    bool check[MAX][MAX];
    int dx[4] = { -1, 0, 0, 1 };
    int dy[4] = { 0, -1, 1, 0 };
    struct shark {
    int r, c, size, cnt= 0;
    }baby;
    bool isBound(int r, int c) {
    if (r > -1 && c > -1 && r < n && c < n) return true;
    return false;
    }
    void selectPrey(queue<pair<int, int>> &prey, queue<pair<int, int>> &q) {
    int prior_x = n, prior_y = n;
    while (!prey.empty()) {
    int x = prey.front().first;
    int y = prey.front().second;
    prey.pop();
    if (x < prior_x) {
    prior_x = x;
    prior_y = y;
    }
    else if (x == prior_x) {
    if (y < prior_y) {
    prior_y = y;
    }
    }
    }
    map[baby.r][baby.c] = 0;
    map[prior_x][prior_y] = 9;
    baby.r = prior_x;
    baby.c = prior_y;
    baby.cnt++;
    if (baby.cnt == baby.size) {
    baby.size++;
    baby.cnt = 0;
    }
    q.push({ prior_x, prior_y });
    }
    void findPrey(int row, int col) {
    queue<pair<int, int>> q, prey;
    q.push({ row, col });
    check[row][col] = true;
    int part_time = 0;
    while (!q.empty()) { // 순회할 때마다 1초 증가
    part_time++;
    int len = q.size();
    for (int i = 0; i < len; ++i) {
    int r = q.front().first;
    int c = q.front().second;
    q.pop();
    for (int j = 0; j < 4; ++j) {
    int x = r + dx[j];
    int y = c + dy[j];
    if (isBound(x, y) && baby.size >= map[x][y] && !check[x][y]) {
    if (baby.size > map[x][y] && map[x][y] > 0) {
    prey.push({ x, y });
    }
    check[x][y] = true;
    q.push({ x, y });
    }
    }
    }
    if (!prey.empty()) {
    while (!q.empty()) q.pop();
    selectPrey(prey, q);
    time = part_time;
    memset(check, 0, sizeof(check));
    }
    }
    isEnd = true;
    }
    int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; ++i) {
    for (int j = 0; j < n; ++j) {
    scanf("%d", &map[i][j]);
    if (map[i][j] == 9) { // 아기 상어 정보
    baby.r = i;
    baby.c = j;
    baby.size = 2;
    }
    }
    }
    while (!isEnd) {
    findPrey(baby.r, baby.c);
    }
    printf("%d\n", time);
    return 0;
    }
    while (!q.empty()) q.pop(); 라인 때문에 시간초과가 걸린 것 같아 굳이 이럴 필요 없이 먹을 수 있는 물고기가 있다면 selectPrey() 를 호출하여 물고기를 선택하고 시간을 갱신해준 후 return하도록 하였다. 이러면 다시 findPrey() 를 호출하기에 업데이트 된 아기 상어 위치가 queue에 들어가고 작업을 수행한다. q를 비울 필요가 없어짐.

2 Try

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
#include <cstdio>
#include <cstring>
#include <queue>
#define MAX 21
using namespace std;
int n, time;
bool isEnd;
int map[MAX][MAX];
bool check[MAX][MAX];
// 상좌우하
int dx[4] = { -1, 0, 0, 1 };
int dy[4] = { 0, -1, 1, 0 };
struct shark {
int r, c, size, cnt= 0;
}baby;
bool isBound(int r, int c) {
if (r > -1 && c > -1 && r < n && c < n) return true;
return false;
}
void selectPrey(queue<pair<int, int>> &prey) {
int prior_x = n, prior_y = n;
while (!prey.empty()) {
int x = prey.front().first;
int y = prey.front().second;
prey.pop();
if (x < prior_x) {
prior_x = x;
prior_y = y;
}
else if (x == prior_x) {
if (y < prior_y) {
prior_y = y;
}
}
}
map[baby.r][baby.c] = 0;
map[prior_x][prior_y] = 9;
baby.r = prior_x;
baby.c = prior_y;
baby.cnt++;
if (baby.cnt == baby.size) {
baby.size++;
baby.cnt = 0;
}
}
void findPrey(int row, int col) {
queue<pair<int, int>> q, prey;
q.push({ row, col });
memset(check, 0, sizeof(check));
check[row][col] = true;
int part_time = 0;
while (!q.empty()) { // 순회할 때마다 1초 증가
part_time++;
int len = q.size();
for (int i = 0; i < len; ++i) {
int r = q.front().first;
int c = q.front().second;
q.pop();
for (int j = 0; j < 4; ++j) {
int x = r + dx[j];
int y = c + dy[j];
if (isBound(x, y) && baby.size >= map[x][y] && !check[x][y]) {
if (baby.size > map[x][y] && map[x][y] > 0) {
prey.push({ x, y });
}
check[x][y] = true;
q.push({ x, y });
}
}
}
if (!prey.empty()) {
selectPrey(prey);
time += part_time;
return;
}
}
isEnd = true;
}
int main() {
scanf("%d", &n);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
scanf("%d", &map[i][j]);
if (map[i][j] == 9) { // 아기 상어 정보
baby.r = i;
baby.c = j;
baby.size = 2;
}
}
}
while (!isEnd) {
findPrey(baby.r, baby.c);
}
printf("%d\n", time);
return 0;
}

Debug

  • 코드
    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
    #include <cstdio>
    #include <cstring>
    #include <queue>
    #define MAX 21
    using namespace std;
    int n, time;
    bool isEnd;
    int map[MAX][MAX];
    bool check[MAX][MAX];
    // 상좌우하
    int dx[4] = { -1, 0, 0, 1 };
    int dy[4] = { 0, -1, 1, 0 };
    struct shark {
    int r, c, size, cnt= 0;
    }baby;
    bool isBound(int r, int c) {
    if (r > -1 && c > -1 && r < n && c < n) return true;
    return false;
    }
    void selectPrey(queue<pair<int, int>> &prey, queue<pair<int, int>> &q) {
    int prior_x = n, prior_y = n;
    while (!prey.empty()) {
    int x = prey.front().first;
    int y = prey.front().second;
    prey.pop();
    if (x < prior_x) {
    prior_x = x;
    prior_y = y;
    }
    else if (x == prior_x) {
    if (y < prior_y) {
    prior_y = y;
    }
    }
    }
    map[baby.r][baby.c] = 0;
    map[prior_x][prior_y] = 9;
    baby.r = prior_x;
    baby.c = prior_y;
    baby.cnt++;
    if (baby.cnt == baby.size) {
    baby.size++;
    baby.cnt = 0;
    }
    q.push({ prior_x, prior_y });
    /*printf("change %d\n");
    for (int k = 0; k < n; ++k) {
    for (int l = 0; l < n; ++l) {
    printf("%d ", map[k][l]);
    }
    printf("\n");
    }*/
    }
    void findPrey(int row, int col) {
    queue<pair<int, int>> q, prey;
    q.push({ row, col });
    check[row][col] = true;
    int part_time = 0;
    while (!q.empty()) { // 순회할 때마다 1초 증가
    part_time++;
    int len = q.size();
    for (int i = 0; i < len; ++i) {
    int r = q.front().first;
    int c = q.front().second;
    q.pop();
    for (int j = 0; j < 4; ++j) {
    int x = r + dx[j];
    int y = c + dy[j];
    if (isBound(x, y) && baby.size >= map[x][y] && !check[x][y]) {
    if (baby.size > map[x][y] && map[x][y] > 0) {
    prey.push({ x, y });
    }
    check[x][y] = true;
    q.push({ x, y });
    }
    }
    }
    if (!prey.empty()) {
    while (!q.empty()) q.pop();
    selectPrey(prey, q);
    time = part_time;
    memset(check, 0, sizeof(check));
    }
    }
    isEnd = true;
    }

    int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; ++i) {
    for (int j = 0; j < n; ++j) {
    scanf("%d", &map[i][j]);
    if (map[i][j] == 9) { // 아기 상어 정보
    baby.r = i;
    baby.c = j;
    baby.size = 2;
    }
    }
    }
    while (!isEnd) {
    findPrey(baby.r, baby.c);
    }
    printf("%d\n", time);
    return 0;
    }

백준 16235번 나무 재테크

#16235. 나무 재테크

Problem

Solution

  • 시뮬레이션 문제이다.
  • 봄, 여름, 가을, 겨울에 맞게 작업을 수행하면 된다.
  • 단 한 칸에 여러 개의 나무가 존재할 수 있다는 점에 주목하자.

처음에 3차원 벡터로 해당 칸에 나무 정보를 저장하였지만 계절마다 작업을 수행할 때 상당히 비효율적이라 시간초과가 떴다.

→ 문제의 조건을 잘 보면 답이 보인다. (항상 느끼는 거지만 어떤 자료구조를 선택할지가 중요)

  1. 나이가 어린 나무부터 양분을 먹는다. (해당 칸에 있는 나무 정보를 순회할 때 어린 나무부터 접근해야 한다.)
  2. 나무 정보를 처음에 입력받을 때 한 칸에 하나씩만 받는다.
  3. 나무가 추가될 때 나이가 1인 나무가 추가된다.
  4. 나이가 클수록 먼저 죽음(나이만큼 양분을 먹기 때문이다.)

위 조건을 도합하면 deque 가 가장 적합하다. deque<int> tree[10][10]
나이가 어린 나무가 앞에 즉, 오름차순으로 정렬되어 있어야 하는데, 처음에 각 칸에 하나씩 입력 받고 추가될 때 앞부분에 나무를 추가해주면 되기 때문이다. 죽는건 앞에서부터 탐색을 하다가 죽는 나무가 생기면 그 뒤부터 이미 죽은 나무가 되기에 죽을 나무 개수만 카운트하고 뒤에서부터 개수만큼 pop하면 되기 때문이다.

1 Try

  • code
    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
    #include <iostream>
    #include <algorithm>
    #include <vector>
    using namespace std;
    int N, M, K, ans, diff;
    int land[10][10];
    int A[10][10];
    int dx[8] = { -1, -1, 0, 1, 1, 1, 0, -1 };
    int dy[8] = { 0, -1, -1, -1, 0, 1, 1, 1 };
    vector<vector<vector<int>>> v;
    void Input() {
    cin >> N >> M >> K;
    for (int i = 0; i < N; ++i) {
    v.resize(N);
    for (int j = 0; j < N; ++j) {
    v[i].resize(N);
    cin >> A[i][j];
    land[i][j] = 5;
    }
    }
    for (int i = 0; i < M; ++i) {
    int r, c, age;
    cin >> r >> c >> age;
    v[r-1][c-1].push_back(age);
    }
    }
    void Task() {
    // 봄, 여름
    for (int i = 0; i < N; ++i) {
    for (int j = 0; j < N; ++j) {
    if (v[i][j].size() > 1) sort(v[i][j].begin(), v[i][j].end());
    int sum = 0;
    for (int k = 0; k < v[i][j].size(); ++k) {
    if (v[i][j][k] == 0) continue;
    if (land[i][j] - v[i][j][k] < 0) {
    sum += v[i][j][k] / 2;
    v[i][j][k] = 0; // 죽은 표시
    diff++;
    }
    else {
    land[i][j] -= v[i][j][k];
    v[i][j][k]++;
    }
    }
    land[i][j] += sum;
    }
    }
    // 가을, 겨울
    for (int i = 0; i < N; ++i) {
    for (int j = 0; j < N; ++j) {
    for (int k = 0; k < v[i][j].size(); ++k) {
    if (v[i][j][k] == 0) continue;
    if (v[i][j][k] % 5 == 0) {
    for (int dir = 0; dir < 8; ++dir) {
    int r = i + dx[dir];
    int c = j + dy[dir];
    if (r <= -1 || c <= -1 || r >= N || c >= N) continue;
    v[r][c].push_back(1);
    }
    }
    }
    land[i][j] += A[i][j];
    }
    }
    }
    int main() {
    Input();
    while (K--) {
    Task();
    }
    for (int i = 0; i < N; ++i) {
    for (int j = 0; j < N; ++j) {
    ans += v[i][j].size();
    }
    }
    ans -= diff;
    cout << ans << "\n";
    return 0;
    }
    시간초과

2 Try

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
#include <iostream>
#include <deque>
using namespace std;
int N, M, K, ans;
int land[10][10];
int A[10][10];
int dx[8] = { -1, -1, 0, 1, 1, 1, 0, -1 };
int dy[8] = { 0, -1, -1, -1, 0, 1, 1, 1 };
deque<int> tree[10][10];
void Input() {
cin >> N >> M >> K;
ans = M;
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
cin >> A[i][j];
land[i][j] = 5;
}
}
for (int i = 0; i < M; ++i) {
int r, c, age;
cin >> r >> c >> age;
tree[r - 1][c - 1].push_back(age);
}
}
void Task() {
// 봄, 여름
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
if (tree[i][j].empty()) continue;
int sum = 0, dead_num = 0;
for (auto iter = tree[i][j].begin(); iter != tree[i][j].end(); ++iter) {
if (land[i][j] - *iter < 0) {
sum += (*iter) / 2;
dead_num++;
ans--;
}
else {
land[i][j] -= (*iter);
(*iter)++;
}
}
for (int k = 0; k < dead_num; ++k) tree[i][j].pop_back();
land[i][j] += sum;
}
}
// 가을, 겨울
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
for (auto iter = tree[i][j].begin(); iter != tree[i][j].end(); ++iter) {
if ((*iter) % 5 == 0) {
for (int dir = 0; dir < 8; ++dir) {
int r = i + dx[dir];
int c = j + dy[dir];
if (r <= -1 || c <= -1 || r >= N || c >= N) continue;
tree[r][c].push_front(1);
ans++;
}
}
}
land[i][j] += A[i][j];
}
}
}
int main() {
Input();
while (K--) {
Task();
}
cout << ans << "\n";
return 0;
}

백준 16234번 인구 이동

#16234. 인구 이동

Problem

Solution

  • BFS

N x N 크기의 배열을 전부 탐색하면서 check표시가 되어있지 않은 부분은 인구 이동이 가능한지 확인 작업이 수행된다.

작업 동안 누적 합과 연합에 포함된 나라 수를 계산해야 한다.

  1. queue가 비어있을 때까지 다음을 수행한다.
  2. 현재 위치에서 4방향 탐색 → 범위체크, 탐색할 위치가 미탐색인지 확인
  3. 탐색 가능하면 누적 합, 나라 수 계산, 종료되지 않게 flag 갱신, queue에 넣어준다.
  4. 만약 나라 수가 1보다 크면 누적합과 사람 수를 따로 저장한다.

위 작업이 끝나면 이제 따로 저장한 누적합과 사람 수를 이용해 N x N 크기의 배열을 바꿔준다. (실질적 인구 이동)

만약 flag가 false 즉, 인구이동이 없다면 종료한다.

  • DFS

BFS보다 훨씬 빠르고 깔끔하며 명료하다. 처음에 BFS로 할 생각을 했던 것은 N x N 크기의 배열 값을 미리 바꾸면 인구 이동에 영향을 미치게 된다는 생각에 코드를 작성했었는데 생각해보니 한 번 탐색이 끝나면(BFS든 DFS든) check 표시 되어 있기에 영향을 주지 않고 한 번 탐색할 때 위치만 미리 벡터에 저장해주면 된다.

BFS 1 Try

  • code
    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 <cstdio>
    #include <cstring>
    #include <cmath>
    #include <queue>
    #include <tuple>
    using namespace std;
    int N, L, R, idx, ans;
    bool no_end;
    int map[51][51];
    int check[51][51]; // 1, 2, 3
    int val[1251][2]; // 값, 사람 수
    int dx[4] = { -1, 1, 0, 0 };
    int dy[4] = { 0, 0, -1, 1 };
    bool isBound(int x, int y) {
    if (x > -1 && y > -1 && x < N && y < N) return true;
    return false;
    }
    void movePeople(int r, int c) {
    idx++;
    int sum = map[r][c], cnt = 1;
    queue<pair<int, int>> q;
    q.push({ r, c });
    check[r][c] = idx;
    while (!q.empty()) {
    int x, y;
    tie(x, y) = q.front();
    q.pop();
    for (int dir = 0; dir < 4; ++dir) {
    int d_x = x + dx[dir];
    int d_y = y + dy[dir];
    if (isBound(d_x, d_y) && check[d_x][d_y] == 0) {
    if (abs(map[x][y] - map[d_x][d_y]) >= L && abs(map[x][y] - map[d_x][d_y]) <= R) {
    check[d_x][d_y] = idx;
    q.push({ d_x, d_y });
    no_end = true;
    cnt++; sum += map[d_x][d_y];
    }
    }
    }
    }
    if (cnt > 1) {
    val[idx][0] = sum;
    val[idx][1] = cnt;
    }
    else {
    check[r][c] = 0;
    idx--;
    }
    }
    void changePeople() {
    bool no_move = true;
    for (int i = 0; i < N; ++i) {
    for (int j = 0; j < N; ++j) {
    if (check[i][j] == 0) continue;
    map[i][j] = val[check[i][j]][0] / val[check[i][j]][1];
    check[i][j] = 0; no_move = false;
    }
    }
    if (!no_move) ans++;
    }
    int main() {
    scanf("%d %d %d", &N, &L, &R);
    for (int i = 0; i < N; ++i) {
    for (int j = 0; j < N; ++j) {
    scanf("%d", &map[i][j]);
    }
    }
    do {
    idx = 0;
    no_end = false;
    for (int i = 0; i < N; ++i) {
    for (int j = 0; j < N; ++j) {
    if (check[i][j] > 0) continue;
    movePeople(i, j);
    }
    }
    changePeople();
    } while (no_end);
    printf("%d\n", ans);
    return 0;
    }

    DFS 1 Try

    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
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <vector>
    using namespace std;
    int N, L, R, sum, ans;
    int map[51][51];
    bool check[51][51];
    vector<pair<int, int> > v; // 위치 정보
    int dx[4] = { -1, 1, 0, 0 };
    int dy[4] = { 0, 0, -1, 1 };
    bool isBound(int x, int y) {
    if (x > -1 && y > -1 && x < N && y < N) return true;
    return false;
    }
    void dfs(int x, int y) {
    sum += map[x][y];
    v.push_back({ x, y });
    check[x][y] = true;
    for (int dir = 0; dir < 4; ++dir) {
    int d_x = x + dx[dir];
    int d_y = y + dy[dir];
    if (isBound(d_x, d_y) && !check[d_x][d_y]) {
    if (abs(map[x][y] - map[d_x][d_y]) >= L && abs(map[x][y] - map[d_x][d_y]) <= R) {
    dfs(d_x, d_y);
    }
    }
    }
    }
    bool task() {
    bool change = false;
    memset(check, 0, sizeof(check));
    for (int i = 0; i < N; ++i) {
    for (int j = 0; j < N; ++j) {
    if (check[i][j]) continue;
    sum = 0;
    v.clear();
    dfs(i, j);
    if (v.size() == 1) continue;
    for (auto e : v) {
    map[e.first][e.second] = sum / v.size();
    }
    change = true;
    }
    }
    return change;
    }
    int main() {
    scanf("%d %d %d", &N, &L, &R);
    for (int i = 0; i < N; ++i) {
    for (int j = 0; j < N; ++j) {
    scanf("%d", &map[i][j]);
    }
    }
    while (task()) { ans++; }
    printf("%d\n", ans);
    return 0;
    }

백준 15686번 치킨 배달

#15686. 치킨 배달

문제링크

Problem

  • N x N 도시
  • 0: 빈 칸, 1: 집, 2: 치킨 집
  • 도시의 치킨 거리는 모든 집의 치킨 거리의 합
  • 치킨 거리 ( 집: (r1, c1) 치킨집: (r2, c2) )
    집과 가장 가까운 치킨집 사이의 거리를 말한다.

Goal: 치킨집 중에서 최대 M개를 골랐을 때 도시의 치킨 거리 최솟값

Solution

  • 치킨 집을 M개 고르는 모든 조합 구하기

해당 집을 고르면 배열에 넣고 아니면 넣지 않음

1
2
3
4
5
6
7
8
9
10
11
void combination(int idx) { 
if (select_idx == m) {
BFS 이용하여 치킨 거리 구하기
return;
}
if (idx == total_chicken) return;
combination(idx + 1); // 선택하지 않기
select[select_idx++] = idx;
combination(idx + 1); // 선택하기
select_idx--; // 다른 경우의 수를 위해 인덱스 빼주기 (중요)
}

중요라고 되어있는 부분을 작성하지 않으면 모든 경우의 수를 구할 수 없다. 경우의 수가 꼬여버림.

4개 중에 2개를 고른다고 하면

1
2
3
4
5
6
7
8
comb(0)
comb(1)
comb(2)
comb(3)
...
이런식으로 호출이 이루어지기에 (3, 2) 경우가 먼저 완성된다.
완성되고 BFS이용하여 치킨 거리 구하고 return되면 select_idx를 빼준다.
그럼 2자리에 다른 경우가 넣어질 수 있다. (3, 1)

  • 각 조합에 대한 최소한의 치킨 거리 구하기

BFS를 이용하여 한 칸씩 갈 때 1초 증가

그러다 1을 만나면 해당 초를 더 해준다.
BFS를 이용하면 동시에 1을 만나기에 time만 더해주어서는 안된다.
동시에 발견한 집의 수만큼 time을 더해주어야 한다.

모든 1을 만났으면 종료

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
void bfs() {
int time = 0, house = 0, dist = 0;
memset(check, 0, sizeof(check));
queue<pair<int, int>> q;
for (int i = 0; i < select_idx; ++i) {
q.push({ chicken[select[i]].r, chicken[select[i]].c });
while (!q.empty()) {
time++;
int cnt = 0;
int len = q.size();
for (int i = 0; i < len; ++i) {
int r = q.front().first;
int c = q.front().second;
q.pop();
check[r][c] = true;
for (int i = 0; i < 4; ++i) {
int x = r + dx[i];
int y = c + dy[i];
if (isBound(x, y) && !check[x][y]) {
check[x][y] = true;
if (city[x][y] == 1) {
house++; cnt++;
}
q.push({ x, y });
}
}
}
dist += time * cnt; // 해당 초에 만난 집의 수 만큼 이동거리 더해주기
}
}

  • 이 중 가장 최소인 치킨 거리 구하기

    1
    2
    3
    4
    5
    dist += time * cnt; // 해당 초에 만난 집의 수 만큼 이동거리 더해주기
    if (total_house == house) {
    answer = answer > dist ? dist : answer;
    return;
    }

    1 Try

  • 컴파일 에러 (index라는 변수명은 기피하자…) select_idx로 변경

    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 <cstdio>
    #include <cstring>
    #include <queue>
    #define MAX 51
    using namespace std;
    int n, m, total_chicken, total_house, index, answer;
    int city[MAX][MAX];
    bool check[MAX][MAX];
    int select[13];
    int dx[4] = { -1, 1, 0, 0 };
    int dy[4] = { 0, 0, -1, 1 };
    struct INFO {
    int r, c;
    }chicken[13];
    bool isBound(int r, int c) {
    if (r > -1 && c > -1 && r < n && c < n) return true;
    return false;
    }
    void bfs() {
    int time = 0, house = 0, dist = 0;
    memset(check, 0, sizeof(check));
    queue<pair<int, int>> q;
    for (int i = 0; i < index; ++i) {
    q.push({ chicken[select[i]].r, chicken[select[i]].c }); // 선택한 치킨집 queue에 저장
    }
    while (!q.empty()) {
    time++;
    int cnt = 0;
    int len = q.size();
    for (int i = 0; i < len; ++i) {
    int r = q.front().first;
    int c = q.front().second;
    q.pop();
    check[r][c] = true;
    for (int i = 0; i < 4; ++i) {
    int x = r + dx[i];
    int y = c + dy[i];
    if (isBound(x, y) && !check[x][y]) {
    check[x][y] = true;
    if (city[x][y] == 1) {
    house++; cnt++;
    }
    q.push({ x, y });
    }
    }
    }
    dist += time * cnt; // 해당 초에 만난 집의 수 만큼 이동거리 더해주기
    if (total_house == house) {
    answer = answer > dist ? dist : answer;
    return;
    }
    }
    }
    void combination(int idx) { // M개를 선택하는 모든 경우의 수 구하기
    if (index == m) {
    bfs();
    return;
    }
    if (idx == total_chicken) return;
    combination(idx + 1);
    select[index++] = idx;
    combination(idx + 1);
    index--;
    }
    int main() {
    scanf("%d %d", &n, &m);
    for (int i = 0; i < n; ++i) {
    for (int j = 0; j < n; ++j) {
    scanf("%d", &city[i][j]);
    if (city[i][j] == 2) { // 치킨집 정보 저장
    chicken[total_chicken].r = i;
    chicken[total_chicken++].c = j;
    }
    else if (city[i][j] == 1) total_house++;
    }
    }
    answer = 1e9;
    combination(0);
    printf("%d\n", answer);
    return 0;
    }

    2 Try

    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 <cstdio>
    #include <cstring>
    #include <queue>
    #define MAX 51
    using namespace std;
    int n, m, total_chicken, total_house, select_idx, answer;
    int city[MAX][MAX];
    bool check[MAX][MAX];
    int select[13];
    int dx[4] = { -1, 1, 0, 0 };
    int dy[4] = { 0, 0, -1, 1 };
    struct INFO {
    int r, c;
    }chicken[13];
    bool isBound(int r, int c) {
    if (r > -1 && c > -1 && r < n && c < n) return true;
    return false;
    }
    void bfs() {
    int time = 0, house = 0, dist = 0;
    memset(check, 0, sizeof(check));
    queue<pair<int, int>> q;
    for (int i = 0; i < select_idx; ++i) {
    q.push({ chicken[select[i]].r, chicken[select[i]].c }); // 선택한 치킨집 queue에 저장
    }
    while (!q.empty()) {
    time++;
    int cnt = 0;
    int len = q.size();
    for (int i = 0; i < len; ++i) {
    int r = q.front().first;
    int c = q.front().second;
    q.pop();
    check[r][c] = true;
    for (int i = 0; i < 4; ++i) {
    int x = r + dx[i];
    int y = c + dy[i];
    if (isBound(x, y) && !check[x][y]) {
    check[x][y] = true;
    if (city[x][y] == 1) {
    house++; cnt++;
    }
    q.push({ x, y });
    }
    }
    }
    dist += time * cnt; // 해당 초에 만난 집의 수 만큼 이동거리 더해주기
    if (total_house == house) {
    answer = answer > dist ? dist : answer;
    return;
    }
    }
    }
    void combination(int idx) { // M개를 선택하는 모든 경우의 수 구하기
    if (select_idx == m) {
    bfs();
    return;
    }
    if (idx == total_chicken) return;
    combination(idx + 1);
    select[select_idx++] = idx;
    combination(idx + 1);
    select_idx--;
    }
    int main() {
    scanf("%d %d", &n, &m);
    for (int i = 0; i < n; ++i) {
    for (int j = 0; j < n; ++j) {
    scanf("%d", &city[i][j]);
    if (city[i][j] == 2) { // 치킨집 정보 저장
    chicken[total_chicken].r = i;
    chicken[total_chicken++].c = j;
    }
    else if (city[i][j] == 1) total_house++;
    }
    }
    answer = 1e9;
    combination(0);
    printf("%d\n", answer);
    return 0;
    }

백준 15685번 드래곤 커브

#15685. 드래곤 커브

문제링크

Problem

  • 좌표 평면
  • 3가지 속성
  1. 시작 점
  2. 시작 방향
    0: x 좌표 증가 → 방향
    1: y 좌표 감소 ↑ 방향
    2: x 좌표 감소 ← 방향
    3: y 좌표 증가 ↓ 방향
  3. 세대
  • 0세대: 길이가 1인 선분
  • 1세대: 0세대 드래곤 커브 끝 점을 기준으로 시계 방향 90도 회전시켜 0세대 끝 점에 붙인 것
  • 2세대: 1세대를 이용하여 1세대를 만든 것처럼 만든다.
  • N세대: N-1세대 커브를 끝 점 기준으로 90도 시계 방향 회전시킨 것을 붙인 것
  • 입력

    드래곤 커브 개수 N (~20)
    x, y (드래곤 커브 시작 점) ~100
    d (시작 방향)
    g (세대) ~10

드래곤 커브는 서로 겹칠 수 있다.

Goal: 만들어진 드래곤 커브에서 정사각형 4개의 꼭짓점이 모두 드래곤 커브의 일부인 것의 개수를 구하기 (모양이 정사각형이 아니어도 4개의 꼭짓점만 만족하면 된다.)

Solution

  • 끝점을 기준으로 시계방향 90도를 했을 때 각 방향의 이동은 다음과 같다.
  1. 0 → 1
  2. 1 → 2
  3. 2 → 3
  4. 3 → 0

아래 그림 참고.

  • 드래곤 커브를 그려주는 건 check 배열로 수행한다.
    (겹쳐도 되니까 초기화할 필요 없다.)
  • 방향만 배열에 저장해주면 된다.
  • 끝점에서 이동을 수행하니 위치 좌표는 끝점만 알면된다.

    1
    2
    3
    4
    5
    // input : 3 3 0 1
    (3, 3) : 처음 시작 위치
    (4, 3) : 0세대 // 위치 좌표: (4, 3) 방향 0
    (4, 2) : 1세대 // 위치 좌표: (4, 2) 방향 0, 1
    (3, 2), (3, 1) : 2세대 // 위치 좌표: (3, 2) -> (3,1) 방향 0, 1, 2, 1

    2세대 설명: 1세대에서 방향이 [0, 1]로 저장되어 있다. 끝점부터 시작하기에 방향 1이 시계방향으로 90도 회전하면 방향 2가 된다. [0, 1, 2]
    이후 0이 시계방향으로 90도 회전하면 방향 1이된다. [0, 1, 2, 1]

  • 드래곤 커브 위의 규칙대로 그리기

0세대 까지 그려놓고 (0세대가 아니라면) 1세대부터 그린다.

direction 배열에 위의 [0, 1, 2, 1]과 같은 값이 들어간다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int clockwise[4] = { 1, 2, 3, 0 };
void draw(int x, int y, int d, int g) {
int idx = 0;
check[y][x] = true;
direction[idx++] = d;
while(g--) {
int len = idx;
for (int i = len-1; i >= 0; --i) {
d = clockwise[direction[i]];
x += dx[d];
y += dy[d];
if (x > -1 && y > -1 && x < MAX && y < MAX) {
check[y][x] = true;
}
direction[idx++] = d;
}
}
}

  • 4개의 꼭짓점 확인

배열은 최대 100 x 100의 크기를 가지기에 0~99까지 check 값이 존재할 수 있다. (사실상 98까지만 확인하면 된다.)

1 1   (0, 0)을 기준으로 오른쪽, 아래, 오른쪽 아래 대각선만 확인하면 된다.
1 1

1 Try

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 <cstdio>
#define MAX 101
using namespace std;
bool check[MAX][MAX];
int clockwise[4] = { 1, 2, 3, 0 };
int dx[4] = {1, 0, -1, 0};
int dy[4] = { 0, -1, 0, 1 };
int direction[MAX*MAX];
void draw(int x, int y, int d, int g) {
int idx = 0;
check[y][x] = true;
direction[idx++] = d;
while(g--) {
int len = idx;
for (int i = len-1; i >= 0; --i) {
d = clockwise[direction[i]];
x += dx[d];
y += dy[d];
if (x > -1 && y > -1 && x < MAX && y < MAX) {
check[y][x] = true;
}
direction[idx++] = d;
}
}
}
int main() {
int n, x, y, d, g, answer = 0;
scanf("%d", &n);
for (int i = 0; i < n; ++i) {
scanf("%d %d %d %d", &x, &y, &d, &g);
check[y][x] = true;
x += dx[d];
y += dy[d];
draw(x, y, d, g);
}
for (int i = 0; i <= 99; ++i) {
for (int j = 0; j <= 99; ++j) {
if (check[i][j]) {
if (check[i + 1][j] && check[i][j + 1] && check[i + 1][j + 1]) answer++;
}
}
}
printf("%d\n", answer);

return 0;
}

백준 15684번 사다리 조작

#15684. 사다리 조작

Problem

Solution

  • 처음에 감이 안잡혀서 어떻게 풀지 막막했었다.
  • 입력값을 보고 사다리 정보를 어떤 식으로 저장할건지가 첫 스타트이자 포인트다.
    이렇게 data가 보여야 조합도 어떤식으로 구성할지 생각나기 때문이다.
  • 작업은 2개로 나뉜다.
    1. 조합 구하기
    2. 사다리 타기
  • 조합 구하기

조합을 구하기 전에 입력값이 어떻게 들어오나 확인해보자.

a b가 입력되면 a행에 b열 사다리와 b+1열 사다리가 연결된다.
이를 array[a][b] = 1(사다리 있음)으로 표시하면 b+1로 갈 수 있다는 뜻이다.
반대로 b+1지점에서 array[a][b]값이 1인걸 확인하면 b로 갈 수 있다는 뜻이다.

이를 활용하여 조합을 구해보자.

모든 행의 1열부터 N-1열까지 탐색해야 한다.
단, 현재 위치뿐만 아니라 자신의 왼쪽, 오른쪽도 확인해야 한다. (연속으로 설치하지 못하기 때문)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
11열 선택 시 다음 가능한 경우 (N에 표시하는 것은 의미 X - 입력값 생각)
1행 - 1(x), 2(x), 3 ... N-1
2행 - 1, 2, 3 ... N-1
...
H행 - 1, 2, 3 ... N-1

for (int i = idx; i <= H; ++i) {
for (int j = 1; j < N; ++j) {
if (visit[i][j]) continue; // 현재 확인
if (j > 1 && visit[i][j - 1]) continue; // 왼쪽 확인
if (visit[i][j + 1]) continue; // 오른쪽 확인
visit[i][j] = true; // 선택 표시
selectAll(i, cnt + 1); // 다음 선택
visit[i][j] = false;
}
}

1 Try

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
#include <iostream>
using namespace std;
int N, M, H, ans;
bool visit[31][11];
void Input() {
ans = 4;
cin >> N >> M >> H;
for (int i = 0; i < M; ++i) {
int a, b; cin >> a >> b;
visit[a][b] = true; // a행 b - b+1 사다리
}
}
bool Check() {
for (int j = 1; j <= N; ++j) {
int current_num = j;
for (int i = 1; i <= H; ++i) {
if (visit[i][current_num]) { // 오른쪽 사다리로 이동
current_num++;
}
else if (current_num > 1 && visit[i][current_num -1]) { // 왼쪽 사다리로 이동
current_num--;
}
}
if (current_num != j) return false;
}
return true;
}
void selectAll(int idx, int cnt) {
if (cnt > ans) return;
if (cnt == 4) {
return;
}
if (Check()) {
if (ans > cnt) ans = cnt;
return;
}
for (int i = idx; i <= H; ++i) {
for (int j = 1; j < N; ++j) { // 5번 사다리는 확인할 필요 없다. (입력값 생각)
if (visit[i][j]) continue;
if (j > 1 && visit[i][j - 1]) continue;
if (visit[i][j + 1]) continue;
visit[i][j] = true;
selectAll(i, cnt + 1);
visit[i][j] = false;
}
}
}
void Solve() {
Input();
selectAll(1, 0);
if (ans == 4) ans = -1;
cout << ans << "\n";
}
int main() {
Solve();
return 0;
}