2018년 7월 22일 일요일

BOJ 1260번 DFS와 BFS

1. 문제 주소: https://www.acmicpc.net/problem/1260

2. 문제 요약: 정점의 개수 N(1<= N <= 1000), 간선의 개수M(1<= M <= 10_000), 탐색을 시작할 정점의 번호 V가 있고, 임의의 그래프가 주어졌을 때, V를 시작으로 DFS 순서와 BFS 순서를 출력하는 문제이다.

3. 문제 접근 방법: DFS는 재귀로, BFS는 큐를 이용하여 구한다. 새로운 맛을 느끼기 위해 C 말고 다른 언어로 만들어 본다.

4. 주의할 점: BFS와 DFS는 경우에 따라 출력 순서가 달라질 수 있는데, 문제에서는 오름차순으로 출력하라고 했으니 주의해야 한다.

5. 문제에 얽힌 슬픈 이야기: 간선의 개수가 1만 개나 되기 때문에, 2차원 배열로 그래프를 표현하여 풀면 시간초과가 날 것이라는 헛된 예측을 했다.
막상 문제를 풀고, Swift로 이 문제를 푼 다른 사람들의 코드를 보니 다들 2차원 배열로 풀었다. 나만 Vertex 클래스나 Edge 클래스 만들고, Vertex에 Edge 리스트 붙여서 풀었다.

6. Swift(4.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
import Foundation
class Vertex{
    let value:Int
    var links:[Edge]
    
    init(_ value:Int) {
        self.value = value
        links = [Edge]()
    }
    
    func addVertex(vertex:Vertex, weight:Int)
    {
        let temp = Edge(to: vertex, weight: weight)
        links.append(temp)
        links.sort(by: {$0.toVertex.value < $1.toVertex.value})
    }
}
class Edge{
    let toVertex:Vertex
    let weight:Int
    
    init(to vertex:Vertex, weight:Int) {
        toVertex = vertex
        self.weight = weight
    }
}
func dfs(root:Vertex, visited: inout [Bool])
{
    print(root.value, terminator: " ")
    visited[root.value] = true
    for v in root.links
    {
        if !visited[v.toVertex.value]
        {
            dfs(root: v.toVertex, visited: &visited)
        }
    }
    
}
func bfs(root:Vertex, visited: inout [Bool], queue: inout [Vertex])
{
   // print(root.value, terminator: "")
 //   visited[root.value] = true
    queue.append(root)
    visited[root.value] = true
    while(!queue.isEmpty)
    {
        let newRoot = queue.removeFirst()
        print(newRoot.value, terminator: " ")
        for v in newRoot.links
        {
            if !visited[v.toVertex.value]
            {
                queue.append(v.toVertex)
                visited[v.toVertex.value] = true
            }
        }
    }
}
let input = readLine()!.split(separator: " ").map({Int($0)!})
let N = input[0]
let M = input[1]
let V = input[2]
var graph = [Vertex](repeating: Vertex(0), count: N + 1)
var visited = [Bool](repeating: false, count: N + 1)
var queue = [Vertex]()
for _ in 0..<M
{
    let info = readLine()!.split(separator: " ").map({Int($0)!})
    if graph[info[0]].value == 0
    {
        graph[info[0]] = Vertex(info[0])
    }
    if graph[info[1]].value == 0
    {
        graph[info[1]] = Vertex(info[1])
    }
    graph[info[0]].addVertex(vertex: graph[info[1]], weight: 0)
    graph[info[1]].addVertex(vertex: graph[info[0]], weight: 0)
}
dfs(root: graph[V], visited: &visited)
visited = [Bool](repeating: false, count: N + 1)
print("")
bfs(root: graph[V], visited: &visited, queue: &queue)
print("")
cs

2018년 7월 13일 금요일

BOJ 1074번 Z

1. 문제 주소: https://www.acmicpc.net/problem/1074

2. 문제 요약: N, r, c가 주어진다. Z자 모양으로 재귀적으로 정의된 형태에 따라 2^N 크기의 2차원 정사각형 격자에 방문할 때, (r, c)는 몇 번째로 방문하는가?

3. 문제 접근 방법

첫 번째 문제 접근 방법
(r ,c)에 몇 번째로 방문하는지는, 격자를 나타내는 2차원 배열에서 [r][c] 인덱스의 값과 같다.
따라서, 문제에 주어진 대로 격자를 그려 넣은 뒤 (r, c)를 넣는다.
처음에는 Swift로 짰으나, 배열을 가리키는 배열 슬라이스에 할당이 가능하지 않아 C로 바꾸었다.

우선, 만일 2x2 격자인 경우 4번 카운트를 더해서 값을 넣는다. 2x2 격자가 아닌 경우, 격자를 4개로 쪼개서 4개를 Z자 순서에 맞게 다시 같은 함수에 넣는다.
격자를 잘 쪼개기 위해서, 격자 한 변의 길이와, 왼쪽 위의 x, y 좌표값을 함께 넘겨준다.

이렇게 풀면 입력 예제에 맞게 정답이 잘 나온다.

하지만, 실제로 제출하면 메모리 초과가 뜬다.

두 번째 문제 접근 방법
메모리 초과를 보고 충격에 빠져서 게임을 하다 보니, 한 번에 함수를 4번씩 호출할 필요가 없다는 것을 깨달았다. 문제에서 요구하는 것은 (r, c)에 대한 정보이므로, (r, c)와 완전히 관련이 없는 경우 작업을 수행하지 않아도 된다.
대신, 수행하지 않은 작업을 고려하여 카운트를 늘려 주어야 한다. 이때는 격자의 한 변의 크기를 이용한다.

이렇게 풀면 훨씬 빨리 답이잘 나온다.

그리고 제출하면 또 메모리 초과가 뜬다.

세 번째 문제 접근 방법
백준 서버가 터져서 다시 게임을 하다가, 반드시 2차원 배열을 할당하여 직접 그려 보지 않아도 된다는 것을 깨달았다. 두 번째 문제 접근 방법에서도 일부만 그렸듯이, 실제론 그리지 않고 '그렸다고 치고' 카운트 값을 세면 된다.
배열을 동적 할당할 때, O(2^N)이므로 이것이 사라지게 된다.

이렇게 풀면 맞았습니다!!가 뜬다.

4. C 코드

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 <stdio.h>
#include <stdlib.h>
#include <math.h>
int cnt;
int N, r, c;
int size;
void dot(int x, int y, int leng)
{
    if(leng == 2)
    {
        if (x == (r+1) && y == (c+1)) {
            cnt += 0;
        }
        else if(x == (r+1) && y < (c+1)) {
            cnt += 1;
        }
        else if(x < (r+1) && y == (c+1)){
            cnt += 2;
        }
        else if(x < (r+1) && y < (c+1)) {
            cnt += 3;
        }
        
    }
    else
    {
        const int target_x = r + 1;
        const int target_y = c + 1;
        if(target_x < x + leng / 2)
        {
            if(target_y < y + leng / 2)
            {
                dot(x, y, leng / 2); //왼쪽 위
            }
            else
            {
                cnt += leng * leng / 4;
                dot(x, y + leng / 2, leng / 2); // 오른쪽 위
            }
        }
        else
        {
            cnt += leng * leng / 2;
            if(target_y < y + leng / 2)
            {
                dot(x + leng / 2, y, leng / 2); // 왼쪽 아래
            }
            else
            {
                cnt += leng * leng / 4;
                dot(x + leng / 2, y + leng / 2, leng / 2); // 오른쪽 아래
            }
        }
    }
}
int main(void)
{
    scanf("%d %d %d", &N, &r, &c);
    size = pow(2, N);
 //   int **arr = (int**)malloc(sizeof(int*) * (size + 1));
 //   for(int i = 0; i <= size; i++)
 //       arr[i] = (int*)malloc(sizeof(int) * (size + 1));
    dot(1, 1, size);
    
    printf("%d\n", cnt);
}
cs

BOJ1535 안녕

1. 문제 주소: https://www.acmicpc.net/problem/1535

2. 문제 요약: 세준이는 인사를 할 때마다 체력을 잃고, 기쁨을 얻는다. 체력이 0이 되지 않으면서 얻을 수 있는 기쁨을 최대값은?

3. 문제 접근 방법:
그 유명한 배낭 문제인데, 머리가 안 좋아서 이해하는 데 시간이 걸렸다.

인사할 사람의 수와 남아 있는 체력에 대한 정보를 가진 2차원 배열을 이용한다. 인사했다가 체력이 0이 되는 상황이면 이전의 기쁨 최대값을 유지하고, 그렇지 않으면 인사할 경우와 아닐 경우를 비교한다.

4. C 코드
(중간에 주석은 디버깅한다고 넣은 거라 무시해도 됨)
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 <stdio.h>
#include <stdlib.h>
int max(int a, int b)
{
  return a > b ? a : b;
}
int main()
{
  int health = 100;
  int N;
  int L[21= {0};
  int J[21= {0};
  int dp[21][101= {0};
  
  scanf("%d"&N);
  for(int i = 1; i <= N; i++)
    scanf("%d"&L[i]);
  for(int i = 1; i <= N; i++)
    scanf("%d"&J[i]);
  for(int i = 1; i <= N; i++)
    {
      for(int w = 1; w <= health; w++)
    {
      int temp = dp[i-1][w];
      
      if(w - L[i] > 0)
        {
          dp[i][w] = max(temp, dp[i-1][w-L[i]] + J[i]);
        }
      else
        {
          dp[i][w] = temp;
        }
      /*
      if(dp[i][w] == 55)
        {
          printf("*** i: %d, w: %d\n", i, w);
          printf("temp: %d, J[i]: %d, dp: %d\n", temp, J[i], dp[i-1][w-L[i]]);
          printf("dp[i][w]: %d\n", dp[i][w]); 
          break;
        }
      */
    }
    }
  printf("%d\n", dp[N][health]);
  return 0;
}
cs

BOJ 10844 쉬운 계단 수

1. 문제 링크: https://www.acmicpc.net/problem/10844

2. 문제 요약: 인접한 수끼리의 차이가 1인 수를 계단 수라고 한다. N이 주어졌을 때, 길이가 N인 계단 수는 모두 몇 개인가?

3. 문제 접근 방법: 다음과 같이 작은 문제로 나눈다.
길이가 1인 계단수의 개수 -> 길이가 1이고 끝이 0인 계단수의 개수, 길이가 1이고 끝이 1인 계단수의 개수...
길이가 2인 계단수의 개수
길이가 3인 계단수의 개수
...
길이가 N인 계단수의 개수

여기서 중요한 점은, 만일 우리가 길이가 k인 계단수를 구하려고 할 때, 길이가 k-1인 계단수에 대한 계산이 끝났다면 안심하고 그 값을 사용해도 된다는 점이다.
왜냐하면, 어떠한 계단수가 있을 때 그 계단수를 아무렇게나 잘라 내어도 잘려진 두 수 역시 계단수여야 하기 때문이다.
직관적으로 당연하고, 귀류법으로 증명할 수 있다.


4. 문제에 얽힌 슬픈 이야기: 원래는 [쉬운 계단 수]가 아니라 [계단 수] 문제를 풀고 있었는데, 그 문제는 "0에서 9까지의 수가 모두 포함된 계단수"만 세는 문제였다. 그걸 모르고 멍청하게 코딩하다가, 예제-입력 값이 뭔가 이상하다는 걸 깨닫고 [쉬운 계단 수]문제를 찾아 풀었다.
참고로 [계단 수] 문제 정답률이 [쉬운 계단 수]보다 더 높은데, 난이도는 정반대다.


5. C 코드

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
#include <stdio.h>
#include <stdlib.h>
#define divisor 1000000000
int main()
{
  int result = 0;
  int N;
  scanf("%d"&N);
  int **dp = (int**)malloc(sizeof(int** (N + 1));                            
  for(int i = 0; i <= N; i++)
    {
      dp[i] = (int*)malloc(sizeof(int* 10);
      for(int j = 0; j < 10; j++)
    dp[i][j] = 0;
    }
  for(int i = 1; i < 10; i++)
    dp[1][i] = 1;
  for(int i = 2; i <= N; i++)
    {
      for(int j = 0; j < 10; j++)
    {
      if(j - 1 >= 0)
        {
          dp[i][j] += dp[i - 1][j - 1];
          dp[i][j] %= divisor;
        }
      if(j + 1 <= 9)
        {
          dp[i][j] += dp[i - 1][j + 1];
          dp[i][j] %= divisor;
        }
    }
    }
  for(int i = 0; i < 10; i++)
    {
      result += dp[N][i];
      result %= divisor;
    }
  printf("%d\n", result);
  
  return 0;
}
cs

BOJ 11052 붕어빵 판매하기

1. 문제 링크: https://www.acmicpc.net/problem/11052

2. 문제 요약: 가지고 있는 붕어빵의 개수가 주어지고, 붕어빵을 묶음으로 팔 때 얼마를 받게 되는지가 주어진다. 붕어빵이 모두 팔린다는 가정하에, 어떻게 세트 메뉴를 구성해야 수익이 극대화되는가?

3. 문제 접근법
작은 문제로 하나씩 나눈다.
붕어빵 1개를 팔 때 극대화되는 수익
붕어빵 2개를 팔 때 극대화되는 수익
붕어빵 n개를 팔 때 극대화되는 수익

임의의 수 k에 대하여 k개의 붕어빵을 팔아야 하고, k-1개 붕어빵에 대해서는 이미 답을 알고 있다고 하자.
그렇다면,
붕어빵 1개 세트를 추가로 파는 경우, 붕어빵 2개 세트를 추가로 파는 경우, ... 붕어빵 k개 세트를 추가로 파는 경우 중 최대값을 찾는다.
이때, k-1에서 k개로 넘어오면서 추가된 붕어빵은 1개이므로, 붕어빵 2개를 추가로 팔려고 하면 2개를 추가로 팔되, 이전까지 판매한 수익은 k-1개 기준이 아니라 k-2개 기준이 되어야 한다.
이것은 3개, 4개 ...k개 통째로 세트로 파는 경우 등에도 그대로 적용된다.

4. C코드

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
#include <stdio.h>                                                                                                
#include <stdlib.h>
int max(int a, int b)
{
  return a > b ? a : b;
}
int main()
{
  int N;
  scanf("%d"&N);
  int *= (int*)malloc(sizeof(int* (N + 1));
  int *dp = (int*)malloc(sizeof(int* (N + 1));
  S[0= 0;
  for(int i = 1; i <= N; i++)
    {
      scanf("%d"&S[i]);
      dp[i] = 0;
    }
  dp[1= S[1];
  for(int i = 2; i <= N; i++)
    {
      dp[i] += S[i] + max(dp[i - 2], dp[i - 3+ S[i - 1]);
    }
  printf("%d\n", dp[N]);
  return 0;
}
cs

BOJ 2579 계단 오르기

1. 문제 링크: https://www.acmicpc.net/problem/2579

2. 문제 요약: 각 계단마다 점수가 주어져 있고, 다음 규칙에 따라 계단을 오를 경우, 점수의 최대값을 구하는 문제이다.
<조건>
 계단을 오를 때, 다음 계단으로 가거나, 다음 다음 계단으로 오를 수 있다.
 3개의 연속된 계단에서 모두 점수를 얻을 수는 없다

3. 문제 접근법
 문제를 다음과 같이 분할하자.
1번째 계단이 목표일 때 얻을 수 있는 최대값
2번째 계단이 목표일 때 얻을 수 있는 최대값
...
N번째 계단이 목표일 때 없을 수 있는 최대값

차근차근 계산을 하되, 주어진 마지막 n번째 계단은 반드시 밟아야 하므로 그 계단을 기준으로 가능한 경우의 수는 다음과 같다.
첫째, 마지막 계단을 밟되, 이전에 밟은 계단은 마지막 계단의 이전 이전 계단인 경우
둘째, 마지막 계단을 밟되, 이전에 밟은 계단은 마지막 계단의 이전 계단이고, 그 전에 밟은 계단은 마지막 기준으로 이전 이전 이전 계단인 경우

max를 이용하여 각 문제를 차근차근 풀고, 최종적으로 원하는 값은 N번째로 구한 값이 된다.

C 코드

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
#include <stdio.h>                                                                                                
#include <stdlib.h>
int max(int a, int b)
{
  return a > b ? a : b;
}
int main()
{
  int N;
  scanf("%d"&N);
  int *= (int*)malloc(sizeof(int* (N + 1));
  int *dp = (int*)malloc(sizeof(int* (N + 1));
  S[0= 0;
  for(int i = 1; i <= N; i++)
    {
      scanf("%d"&S[i]);
      dp[i] = 0;
    }
  dp[1= S[1];
  for(int i = 2; i <= N; i++)
    {
      dp[i] += S[i] + max(dp[i - 2], dp[i - 3+ S[i - 1]);
    }
  printf("%d\n", dp[N]);
  return 0;
}
cs

BOJ 9090025 큐

1. 문제 링크: https://www.acmicpc.net/problem/10845
2. 문제 요약: 간단한 큐 구현
3. 문제 접근법: 큐가 무엇인지 알면 된다. C를 사용할 경우, 배열을 매우 크게 잡거나 연결 리스트를 구현하자.

Ruby(2.5) 코드
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
def push(queue, x)
  queue << x
end
def pop(queue)
  if queue.any?
    puts queue.shift(1)
  else
    puts -1
  end
end
def size(queue)
  puts queue.length
end
def empty(queue)
  if queue.any?
    puts 0
  elsif
    puts 1
  end
end
def front(queue)
  if queue.any?
    puts queue[0]
  elsif
    puts -1
  end
end
def back(queue)
  if queue.any?
    puts queue[-1]
  elsif
    puts -1
  end
end
  
data = Array.new()
= gets.to_i
t.times do
  args = gets.split(" ")
  case args[0]
  when "push" then
    push(data, args[1])
  when "pop" then
    pop(data)
  when "front" then
    front(data)
  when "back" then
    back(data)
  when "empty" then
    empty(data)
  when "size" then
    size(data)
  else
  end
end
cs