www.acmicpc.net/problem/17472

 

17472번: 다리 만들기 2

첫째 줄에 지도의 세로 크기 N과 가로 크기 M이 주어진다. 둘째 줄부터 N개의 줄에 지도의 정보가 주어진다. 각 줄은 M개의 수로 이루어져 있으며, 수는 0 또는 1이다. 0은 바다, 1은 땅을 의미한다.

www.acmicpc.net

 

이 문제는 DFS, MST(최소신장트리) 를 이용해 푸는 문제이다. 질문 게시판을 보니 삼성 코딩테스트 기출문제인듯 하다. 위 문제 풀이 방법 중 핵심은 인접한 셀들을 "섬"으로써 잘 Grouping 하여, 다른 "섬"(group)과의 최단거리를 찾아야 한다

 

일단 필요한 알고리즘들을 생각해보면,

   1. DFS : 섬의 개수 세기

   2. MST(최소신장트리) : 모든 섬들을 연결하는 최단 간선 구하기 

이다.

 

MST 에는 크루스칼(Kruskal) 알고리즘, 프림(prim) 알고리즘이 있다. 

 

크루스칼 알고리즘

간선의 가중치를 정렬해 작은 것 부터 차례로 선택해나가는 방법

1. 간선들을 가중치의 오름차순으로 정렬한다
2. 정렬된 간선 중 사이클을 형성하지 않는 간선을 선택
3. 해당 간선을 현재 MST 집합에 추가
4. 1~3 과정을 "|정점 수| - 1" 번 반복  // V 개의 정점을 연결하기 위해 필요한 간선은 V-1개 이므로

 

 

프림 알고리즘

하나의 정점을 선택해 가장 가중치가 작은 간선들을 차례로 연결해나가는 방법

1.  시작 노드(어떤 노드든 상관없음) 를 MST 집합에 포함
2.  1단계에서 만들어진 MST 집합에 인접한 노드들 중 최소 간선으로 연결된 노드를 선택해 MST 집합에 포함
3.  1~2 과정을 "|정점 수| - 1" 번 반복  // V 개의 정점을 연결하기 위해 필요한 간선은 V-1개 이므

 

 

프림알고리즘의 구현을 위해선 priority_queue(우선순위 큐)가 필요할 수 있지만, 본 문제에서는 간선을 우리가 만들어가며 찾아야하기 때문에 BFS(너비우선탐색)를 통해 찾는 것이 적절하다. 같은 이유로 Kruskal 알고리즘을 사용하기 복잡하다.

 

대략 알고리즘을 설명하면 다음과 같다.

 

1. 섬의 갯수 찾기 (DFS)

    - 각 섬의 Grouping을 위해 ID 를 부여한다.

2. 섬의 갯수-1 번 반복하여 다른 그룹의 섬을 잇는 간선 찾기 (BFS)

    - 1번 섬을 기준으로 BFS를 통해 최단거리의 다른 섬(그룹)을 찾으면 1번 그룹에 병합 (Union-Find 개념)

    * 탐색 할 때는 가로/세로 방향 유지

 

* Union-Find 알고리즘 참고 : https://gmlwjd9405.github.io/2018/08/31/algorithm-union-find.html

 

 

소스코드

  1. #include<stdio.h>
  2. #include<vector>
  3. #include<queue>
  4. #include<string.h>
  5. using namespace std;
  6.  
  7. struct node{
  8. pair<int,int> cur;
  9. int cnt_bridge, is_vertical;
  10. node(pair<int,int> cur, int cnt, int is_v) : cur(cur), cnt_bridge(cnt), is_vertical(is_v) {};
  11. };
  12.  
  13. int n,m, dx[4]= { -1,1,0,0}, dy[4] = {0,0,-1,1}, arr[11][11];
  14. bool visited[11][11][2];
  15. vector< pair<int,int> > island[7];
  16.  
  17. void find_island(int idx, int y,int x){
  18. visited[y][x][0] = 1;
  19. island[idx].push_back(make_pair(y,x));
  20. arr[y][x] = idx+1;
  21. for(int i=0;i<4;i++){
  22. int ny = y + dy[i], nx = x+dx[i];
  23. if(ny>=0 && nx >=0 && nx < m && ny < n){
  24. if(!visited[ny][nx][0] && arr[ny][nx]){
  25. find_island(idx, ny, nx);
  26. }
  27. }
  28. }
  29. }
  30.  
  31. void push_queue(queue< node >& q, int island_idx=1){
  32. int idx = island_idx-1; // 1번 그룹
  33.  
  34. for(int i=0; i< island[idx].size(); i++){
  35. q.push(node(island[idx][i], 0, -1));
  36. }
  37. // printf("push queue #%d, queue_size : %d\n", island_idx, q.size());
  38. }
  39.  
  40. void Union(int found_island_idx){
  41. int& idx = found_island_idx;
  42. for(int i=0;i<n; i++){
  43. for(int j=0; j<m;j++){
  44. if(arr[i][j] == idx){
  45. island[0].push_back(make_pair(i,j));
  46. arr[i][j] = 1;
  47. }
  48. }
  49. }
  50.  
  51. // printf("union between 1 and %d\n\n", idx);
  52. // for(int i=0;i<n; i++){
  53. // for(int j=0; j<m;j++){
  54. // printf("%d ",arr[i][j]);
  55. // }
  56. // printf("\n");
  57. // }
  58. }
  59.  
  60. int main(){
  61. scanf("%d%d",&n,&m);
  62. for(int i=0;i<n;i++){
  63. for(int j=0; j<m; j++){
  64. scanf("%d", &arr[i][j]);
  65. }
  66. }
  67. int cnt_island = 0;
  68. for(int i=0; i<n;i++){
  69. for(int j=0; j<m;j++){
  70. if(!visited[i][j][0] && arr[i][j]){
  71. find_island(cnt_island, i,j);
  72. cnt_island += 1;
  73. }
  74. }
  75. }
  76.  
  77. // printf("\n");
  78. // for(int i=0; i<n;i++){
  79. // for(int j=0; j<m;j++){
  80. // printf("%d ", arr[i][j]);
  81. // }
  82. // printf("\n");
  83. // }
  84. // cnt_islend-1 edges are needed to connect all island
  85. int ans = 0, edges = 0;
  86. for(int i=0; i<cnt_island-1; i++){
  87. memset(visited, 0, sizeof(visited));
  88. queue< node > q;
  89. push_queue(q, 1);
  90.  
  91. while(!q.empty()){
  92. node cur = q.front(); q.pop();
  93. int cury = cur.cur.first, curx = cur.cur.second;
  94.  
  95. // another island check
  96. if(arr[cury][curx] > 1 && cur.cnt_bridge >= 2) {
  97. // printf("found another island#%d at (%d,%d): %d\n", arr[cury][curx], cury,curx, cur.cnt_bridge);
  98. int island_idx = arr[cury][curx];
  99. ans += cur.cnt_bridge;
  100. edges += 1;
  101. Union(island_idx);
  102. break;
  103. }
  104.  
  105. // cannot pass through another island
  106. if(arr[cury][curx] > 1){
  107. continue;
  108. }
  109.  
  110. if(cur.is_vertical == -1){
  111. visited[ cury ][ curx ][0] = visited[ cury ][ curx ][1] = 1;
  112. } else {
  113. visited[ cury ][ curx ][cur.is_vertical] = 1;
  114. }
  115.  
  116. // build bridge to search island
  117. for(int d=0; d<4;d++){
  118. int ny = cury + dy[d], nx = curx + dx[d];
  119. if(ny >= 0 && nx >=0 && nx < m && ny < n && arr[ny][nx] != 1 && !visited[ny][nx][cury!=ny]){
  120. if( cur.is_vertical == -1 ){ // all directions
  121. q.push(node(make_pair(ny,nx), cur.cnt_bridge+(arr[cury][curx] == 0?1:0), cury != ny));
  122. } else if (cur.is_vertical == 1 && cury != ny){
  123. q.push(node(make_pair(ny,nx), cur.cnt_bridge+(arr[cury][curx] == 0?1:0), 1));
  124. } else if (cur.is_vertical == 0 && cury == ny) { // horizontal
  125. q.push(node(make_pair(ny,nx), cur.cnt_bridge+(arr[cury][curx] == 0?1:0), 0));
  126. }
  127. }
  128. }
  129. }
  130. }
  131. printf("%d\n", (ans == 0 || edges != cnt_island-1) ? -1 : ans);
  132. return 0;
  133. }
  134.  

 

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

BOJ 1969 DNA  (0) 2021.05.24
BOJ 2529 부등호  (0) 2021.05.19
BOJ 1305 광고  (0) 2020.09.24
BOJ 10826 피보나치 수 4  (0) 2020.09.04
1516 게임 개발  (0) 2020.09.04

www.acmicpc.net/problem/1305

 

1305번: 광고

첫째 줄에 광고판의 크기 L이 주어지고, 둘째 줄에 현재 광고판에 보이는 문자열이 주어진다. L은 백만보다 작거나 같은 자연수이다.

www.acmicpc.net

 

위 문제는 길이가 N 인 광고판에 문자열 길이가 L 인 광고가 왼쪽 쉬프트를 하며 무한히 나타날 때, 가능한 문자열의 최소 길이를 구하는 문제이다. 즉, 광고판 길이가 6이고 현재 광고판의 문자열이 "aabaaa" 일때, 가능한 문자열 후보는 "aaba", "aaab", "abaa", "aabaa", "abaaa" 등 다양하지만, 가장 짧은 길이는 4이다.

 

이 문제는 KMP 알고리즘에서 사용하는 pi 배열을 이용하여 풀 수 있다. pi 배열은 접두사(prefix) 와 접미사(suffix)가 같은 부분 문자열 중 가장 길이가 긴 것을 저장한다.

 

"ABADABC" 라는 문자열이 있을 때, pi는 다음과 같이 저장된다.

i 부분 문자열 pi[i]
0 A 0
1 AB 0
2 ABA 1
3 ABAD 0
4 ABADA 1
5 ABADAB 2
6 ABADABC 0

 

KMP는 이 pi배열을 이용해 문자열 검색의 시간 복잡도를 O( |S| * |P| ), (S : 전체 문자열, P : 전체 문자열에서 찾고자하는 패턴(검색) 문자열) 에서 O( |S|+|P| ) 로 줄이는 알고리즘이다.

 

1305 광고문제는 검색까지는 필요없고, Pi만 배열만 있으면 풀 수 있다.

길이가 N인 광고판과 길이가 L (L <= N) 인 문자열이 있을 때, 앞의 X개의 문자열(진한 파란색)은 광고 문자열 L번째 문자 뒤에 반복된다.

 

즉, N길이의 광고판 문자열에 대해 pi 를 구한 후, N-pi[N]을 구하면 답을 구할 수 있다.

 

 

소스코드

소스는 백준님 풀이 방식을 기반으로 구현했습니다.

  1. #include<iostream>
  2. #include<string>
  3. #include<vector>
  4. using namespace std;
  5.  
  6. vector<int> get_pi_array(string s){
  7. int n = s.size();
  8. vector<int>pi(n);
  9. pi[0] = 0;
  10. int j = 0;
  11. for(int i=1; i < n; i++){
  12. while(j>0 && s[i] != s[j]){
  13. j = pi[j-1];
  14. }
  15. if(s[i] == s[j]){
  16. pi[i] = j + 1;
  17. j += 1;
  18. } else {
  19. pi[i] = 0;
  20. }
  21. }
  22. return pi;
  23. }
  24.  
  25. int main(){
  26. int n;
  27. string s;
  28. cin >> n >> s;
  29. vector<int> pi = get_pi_array(s);
  30. cout << n - pi[n-1] << '\n';
  31. return 0;
  32. }

 

 

관련 사이트:

bowbowbow.tistory.com/6

m.blog.naver.com/kks227/220917078260

 

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

BOJ 2529 부등호  (0) 2021.05.19
BOJ 17472 다리만들기 2  (0) 2020.11.24
BOJ 10826 피보나치 수 4  (0) 2020.09.04
1516 게임 개발  (0) 2020.09.04
BOJ 2407 조합  (0) 2020.09.04

https://www.acmicpc.net/problem/10826

 

10826번: 피보나치 수 4

피보나치 수는 0과 1로 시작한다. 0번째 피보나치 수는 0이고, 1번째 피보나치 수는 1이다. 그 다음 2번째 부터는 바로 앞 두 피보나치 수의 합이 된다. 이를 식으로 써보면 Fn = Fn-1 + Fn-2 (n>=2)가 ��

www.acmicpc.net

 

이 문제는 BOJ2407 조합 문제와 같이 피보나치 10000 의 자릿수가 2000 이 넘어가기 때문에 c++ 표준 자료형의 범위로는 표현할 수 없다. 마찬가지로 string 덧셈으로 풀 수 있는데, 이전 BOJ2407 번에서 구현한 string 덧셈이 비효율적이라 시간초과가 난다. 따라서 string 덧셈과정에서 역순으로 += 연산을 최대한 이용할 수 있도록 구현하여 시간제한 안에 풀 수 있도록 구현하였다.

 

새롭게 안 사실인데, 피보나치 4 문제의 질문 게시글 에서 문자열의 덧셈 방법 s = s + "string" 과 s += "string" 이 다르다는 것을 설명한다. 첫 번째 방법(s = s + "string") 은 string 을 새롭게 복사하기 때문에 O(s.size()^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
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
 
string s_add(string s1, string s2){
    string ret = "";
 
    int s1len = s1.length(), s2len = s2.length();
    int is_carry = 0;
    int i;
    for(i = 0; i<s2len; i++){
        char s = s1[i]-'0' + s2[i] + is_carry;
        if(s > '9'){
            is_carry = 1;
            s -= 10;
        } else {
            is_carry = 0;
        }
        ret += s;
    }
 
    for(int j=i; j<s1len; j++){
        char s = s1[j] + is_carry;
        if(s > '9'){
            is_carry = 1;
            s -= 10;
        } else {
            is_carry = 0;
        }
        ret += s;
    }
 
    if(is_carry){
        ret += "1";
    }
    return ret;
}
vector< string >v(10001);
int main(){
    int n;
    cin>>n;
    for(int i=0; i<=10000; i++){
        v[i] = "0";
    }
    v[2= v[1= "1";
    for(int i=3; i<=10000;i++){
        v[i] = s_add(v[i-1], v[i-2]);
    }
    reverse(v[n].begin(), v[n].end());
    cout<< v[n] <<endl;
    return 0;
}
cs

 

 

 

 

 

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

BOJ 17472 다리만들기 2  (0) 2020.11.24
BOJ 1305 광고  (0) 2020.09.24
1516 게임 개발  (0) 2020.09.04
BOJ 2407 조합  (0) 2020.09.04
LCS 최장 공통 부분 수열  (0) 2020.09.03

https://www.acmicpc.net/problem/1516

 

1516번: 게임 개발

첫째 줄에 건물의 종류 수 N(1 ≤ N ≤ 500)이 주어진다. 다음 N개의 줄에는 각 건물을 짓는데 걸리는 시간과 그 건물을 짓기 위해 먼저 지어져야 하는 건물들의 번호가 주어진다. 건물의 번호는 1부

www.acmicpc.net

 

이 문제는 위상정렬로 풀 수 있는 문제이다. 위상 정렬은 주로 게임에서 스킬트리와 같은 즉, 선행되어야 하는 작업이 있을 때의 순서를 어떻게 처리해야 빠른지를 풀 때 사용하는 알고리즘이다. 위상정렬의 구현은 그래프 구조에서 in-direction 의 갯수를 저장하는 배열이 가장 핵심이다. 즉 어떤 노드 K 번에 들어오는 간선의 수가 3개가 있을 때, ind[K] = 3 처럼 저장한 뒤, ind[i] 값이 0인 정점부터 큐(queue)에 넣고 순차적으로 탐색해가면 된다.

 

 

위 그림으로 설명하면

 1. ind 값이 0 인 1번 3번 노드가 queue에 넣는다.
 2. 큐에 가장 앞에 있는 노드를 방문(1 번) 한 후, 이 노드에 연결된 (2 번으로의)간선을 돌면서 ind[ graph[cur][next] ] 의 값을 -1 해준다.
     ( 그림에선 2번으로의 간선이므로, ind[2]--; )
 3. 만약 2번의 과정을 통해서 ind 값이 0 이 되면(ind[ graph[cur][next] ] == 0) 해당 노드를 queue에 push 한다.
 4. 2~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
#include<stdio.h>
#include<queue>
#include<vector>
 
using namespace std;
int inline max(int a, int b){return a>b?a:b;}
int n, t[501], ind[501], ans[501];
vector<int> v[501];
 
int main(){
    scanf("%d"&n);
    int p;
    for(int i=1;i<=n;i++){
        scanf("%d"&t[i]);
        while(scanf("%d"&p) == 1){
            if(p == -1break;
            v[p].push_back(i);
            ind[i]++;
        }
    }
 
    queue< pair<intint> > q;
    for(int i=1;i<=n;i++){
        if(ind[i] == 0){
            q.push(make_pair(i, t[i]));
            ans[i] = t[i];
        }
    }
 
    while(!q.empty()){
        int cur = q.front().first; 
        int curt = q.front().second;
        q.pop();
        for(int i=0; i<v[cur].size(); i++){
            ind[ v[cur][i] ]--;
            ans[ v[cur][i] ] = max(ans[v[cur][i]], curt + t[ v[cur][i] ]);
            if(ind[ v[cur][i] ] == 0){
                q.push(make_pair(v[cur][i], ans[ v[cur][i] ] ));
            }
        }
    }
    for(int i=1;i<=n;i++){
        printf("%d\n", ans[i]);
    }
    return 0;
}
 
 
cs

 

 

 

 

 

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

BOJ 1305 광고  (0) 2020.09.24
BOJ 10826 피보나치 수 4  (0) 2020.09.04
BOJ 2407 조합  (0) 2020.09.04
LCS 최장 공통 부분 수열  (0) 2020.09.03
BOJ 가장 긴 증가하는 부분 수열  (0) 2020.09.02

https://www.acmicpc.net/problem/2407

 

2407번: 조합

n과 m이 주어진다. (5 ≤ n ≤ 100, 5 ≤ m ≤ 100, m ≤ n)

www.acmicpc.net

 

이 문제는 100 C 50 조합 값이 long long int 를 벗어나는 약 30자리 수 정도이기 때문에, 다른 방법으로 덧셈을 계산해야 한다. 따라서 덧셈을 위해 string 클래스를 이용해 문자열끼리의 더하기를 구현하였다.

 

다음은 100_C_n 의 조합의 결과 : 

(100C1) : 1 + 99 = 100
(100C2) : 99 + 4851 = 4950
(100C3) : 4851 + 156849 = 161700
(100C4) : 156849 + 3764376 = 3921225
(100C5) : 3764376 + 71523144 = 75287520
(100C6) : 71523144 + 1120529256 = 1192052400
(100C7) : 1120529256 + 14887031544 = 16007560800
(100C8) : 14887031544 + 171200862756 = 186087894300
(100C9) : 171200862756 + 1731030945644 = 1902231808400
(100C10) : 1731030945644 + 15579278510796 = 17310309456440
(100C11) : 15579278510796 + 126050526132804 = 141629804643600
(100C12) : 126050526132804 + 924370524973896 = 1050421051106700
(100C13) : 924370524973896 + 6186171974825304 = 7110542499799200
(100C14) : 6186171974825304 + 38000770702498296 = 44186942677323600
(100C15) : 38000770702498296 + 215337700647490344 = 253338471349988640
(100C16) : 215337700647490344 + 1130522928399324306 = 1345860629046814650
(100C17) : 1130522928399324306 + 5519611944537877494 = 6650134872937201800
(100C18) : 5519611944537877494 + 25144898858450330806 = 30664510802988208300
(100C19) : 25144898858450330806 + 107196674080761936594 = 132341572939212267400
(100C20) : 107196674080761936594 + 428786696323047746376 = 535983370403809682970
(100C21) : 428786696323047746376 + 1613054714739084379224 = 2041841411062132125600
(100C22) : 1613054714739084379224 + 5719012170438571889976 = 7332066885177656269200
(100C23) : 5719012170438571889976 + 19146258135816088501224 = 24865270306254660391200
(100C24) : 19146258135816088501224 + 60629817430084280253876 = 79776075565900368755100
(100C25) : 60629817430084280253876 + 181889452290252840761628 = 242519269720337121015504
(100C26) : 181889452290252840761628 + 517685364210719623706172 = 699574816500972464467800
(100C27) : 517685364210719623706172 + 1399667836569723427057428 = 1917353200780443050763600
(100C28) : 1399667836569723427057428 + 3599145865465003098147672 = 4998813702034726525205100
(100C29) : 3599145865465003098147672 + 8811701946483283447189128 = 12410847811948286545336800
(100C30) : 8811701946483283447189128 + 20560637875127661376774632 = 29372339821610944823963760
(100C31) : 20560637875127661376774632 + 45764000431735762419272568 = 66324638306863423796047200
(100C32) : 45764000431735762419272568 + 97248500917438495140954207 = 143012501349174257560226775
(100C33) : 97248500917438495140954207 + 197443926105102399225573693 = 294692427022540894366527900
(100C34) : 197443926105102399225573693 + 383273503615787010261407757 = 580717429720889409486981450
(100C35) : 383273503615787010261407757 + 711793649572175876199757263 = 1095067153187962886461165020
(100C36) : 711793649572175876199757263 + 1265410932572757113244012912 = 1977204582144932989443770175
(100C37) : 1265410932572757113244012912 + 2154618614921181030658724688 = 3420029547493938143902737600
(100C38) : 2154618614921181030658724688 + 3515430371713505892127392912 = 5670048986634686922786117600
(100C39) : 3515430371713505892127392912 + 5498493658321124600506947888 = 9013924030034630492634340800
(100C40) : 5498493658321124600506947888 + 8247740487481686900760421832 = 13746234145802811501267369720
(100C41) : 8247740487481686900760421832 + 11868699725888281149874753368 = 20116440213369968050635175200
(100C42) : 11868699725888281149874753368 + 16390109145274293016493707032 = 28258808871162574166368460400
(100C43) : 16390109145274293016493707032 + 21726423750712434928840495368 = 38116532895986727945334202400
(100C44) : 21726423750712434928840495368 + 27651812046361280818524266832 = 49378235797073715747364762200
(100C45) : 27651812046361280818524266832 + 33796659167774898778196326128 = 61448471214136179596720592960
(100C46) : 33796659167774898778196326128 + 39674339023040098565708730672 = 73470998190814997343905056800
(100C47) : 39674339023040098565708730672 + 44739148260023940935799206928 = 84413487283064039501507937600
(100C48) : 44739148260023940935799206928 + 48467410615025936013782474172 = 93206558875049876949581681100
(100C49) : 48467410615025936013782474172 + 50445672272782096667406248628 = 98913082887808032681188722800
(100C50) : 50445672272782096667406248628 + 50445672272782096667406248628 = 100891344545564193334812497256
(100C51) : 50445672272782096667406248628 + 48467410615025936013782474172 = 98913082887808032681188722800
(100C52) : 48467410615025936013782474172 + 44739148260023940935799206928 = 93206558875049876949581681100
(100C53) : 44739148260023940935799206928 + 39674339023040098565708730672 = 84413487283064039501507937600
(100C54) : 39674339023040098565708730672 + 33796659167774898778196326128 = 73470998190814997343905056800
(100C55) : 33796659167774898778196326128 + 27651812046361280818524266832 = 61448471214136179596720592960
(100C56) : 27651812046361280818524266832 + 21726423750712434928840495368 = 49378235797073715747364762200
(100C57) : 21726423750712434928840495368 + 16390109145274293016493707032 = 38116532895986727945334202400
(100C58) : 16390109145274293016493707032 + 11868699725888281149874753368 = 28258808871162574166368460400
(100C59) : 11868699725888281149874753368 + 8247740487481686900760421832 = 20116440213369968050635175200
(100C60) : 8247740487481686900760421832 + 5498493658321124600506947888 = 13746234145802811501267369720
(100C61) : 5498493658321124600506947888 + 3515430371713505892127392912 = 9013924030034630492634340800
(100C62) : 3515430371713505892127392912 + 2154618614921181030658724688 = 5670048986634686922786117600
(100C63) : 2154618614921181030658724688 + 1265410932572757113244012912 = 3420029547493938143902737600
(100C64) : 1265410932572757113244012912 + 711793649572175876199757263 = 1977204582144932989443770175
(100C65) : 711793649572175876199757263 + 383273503615787010261407757 = 1095067153187962886461165020
(100C66) : 383273503615787010261407757 + 197443926105102399225573693 = 580717429720889409486981450
(100C67) : 197443926105102399225573693 + 97248500917438495140954207 = 294692427022540894366527900
(100C68) : 97248500917438495140954207 + 45764000431735762419272568 = 143012501349174257560226775
(100C69) : 45764000431735762419272568 + 20560637875127661376774632 = 66324638306863423796047200
(100C70) : 20560637875127661376774632 + 8811701946483283447189128 = 29372339821610944823963760
(100C71) : 8811701946483283447189128 + 3599145865465003098147672 = 12410847811948286545336800
(100C72) : 3599145865465003098147672 + 1399667836569723427057428 = 4998813702034726525205100
(100C73) : 1399667836569723427057428 + 517685364210719623706172 = 1917353200780443050763600
(100C74) : 517685364210719623706172 + 181889452290252840761628 = 699574816500972464467800
(100C75) : 181889452290252840761628 + 60629817430084280253876 = 242519269720337121015504
(100C76) : 60629817430084280253876 + 19146258135816088501224 = 79776075565900368755100
(100C77) : 19146258135816088501224 + 5719012170438571889976 = 24865270306254660391200
(100C78) : 5719012170438571889976 + 1613054714739084379224 = 7332066885177656269200
(100C79) : 1613054714739084379224 + 428786696323047746376 = 2041841411062132125600
(100C80) : 428786696323047746376 + 107196674080761936594 = 535983370403809682970
(100C81) : 107196674080761936594 + 25144898858450330806 = 132341572939212267400
(100C82) : 25144898858450330806 + 5519611944537877494 = 30664510802988208300
(100C83) : 5519611944537877494 + 1130522928399324306 = 6650134872937201800
(100C84) : 1130522928399324306 + 215337700647490344 = 1345860629046814650
(100C85) : 215337700647490344 + 38000770702498296 = 253338471349988640
(100C86) : 38000770702498296 + 6186171974825304 = 44186942677323600
(100C87) : 6186171974825304 + 924370524973896 = 7110542499799200
(100C88) : 924370524973896 + 126050526132804 = 1050421051106700
(100C89) : 126050526132804 + 15579278510796 = 141629804643600
(100C90) : 15579278510796 + 1731030945644 = 17310309456440
(100C91) : 1731030945644 + 171200862756 = 1902231808400
(100C92) : 171200862756 + 14887031544 = 186087894300
(100C93) : 14887031544 + 1120529256 = 16007560800
(100C94) : 1120529256 + 71523144 = 1192052400
(100C95) : 71523144 + 3764376 = 75287520
(100C96) : 3764376 + 156849 = 3921225
(100C97) : 156849 + 4851 = 161700
(100C98) : 4851 + 99 = 4950
(100C99) : 99 + 1 = 100

 

 

소스코드

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
#include<stdio.h>
#include<string>
#include<vector>
using namespace std;
int n,m;
vector< string > dp[101];
 
string s_add(string s1, string s2){
    string ret = "";
 
    int s1len = s1.length()-1, s2len = s2.length()-1;
    int is_carry = 0;
    while( s1len >= 0 || s2len >= 0){
        int s = ( s1len >= 0 ? s1[s1len]-'0' : 0 ) + ( s2len >= 0 ? s2[s2len]-'0' : 0+ is_carry;
        is_carry = s / 10;
        s %= 10;
 
        ret = (char)(s+'0'+ ret;
        s1len--, s2len--;
    }
    if(is_carry){
        ret = (char)('0'+is_carry) + ret;
    }
    return ret;
}
 
int main(){
    scanf("%d %d",&n,&m);
    
    for(int i=1; i<=n; i++){
        dp[i].resize(n+1);
        for(int j=1;j<n;j++){
            dp[i][j] = "0";
        }
        dp[i][0= dp[i][i] = "1";
    }
    for(int i=2; i<= n; i++){
        for(int j=1; j < i; j++){
            dp[i][j] = s_add(dp[i-1][j-1], dp[i-1][j]);
            // printf("(%dC%d) : %s + %s = %s\n", i,j, dp[i-1][j-1].c_str(), dp[i-1][j].c_str(), dp[i][j].c_str());
        }
    }
    printf("%s\n", dp[n][m].c_str());
    return 0;
}
cs

 

 

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

BOJ 10826 피보나치 수 4  (0) 2020.09.04
1516 게임 개발  (0) 2020.09.04
LCS 최장 공통 부분 수열  (0) 2020.09.03
BOJ 가장 긴 증가하는 부분 수열  (0) 2020.09.02
BOJ 4811 알약  (0) 2020.08.28

먼저 용어를 간단히 정리하자면, LCS(Longest Common Subsequence) 로 Subsequence 는 부분 수열이기 때문에, 꼭 연속적인 문자열일 필요가 없다. 만약 substring 이면 연속적인 문자열이다.

 

 

O(N^2)

https://www.acmicpc.net/problem/9251

 

9251번: LCS

LCS(Longest Common Subsequence, 최장 공통 부분 수열)문제는 두 수열이 주어졌을 때, 모두의 부분 수열이 되는 수열 중 가장 긴 것을 찾는 문제이다. 예를 들어, ACAYKP와 CAPCAK의 LCS는 ACAK가 된다.

www.acmicpc.net

 

DP 정의는 다음과 같다.

dp[p][q] : 첫 번째 문자열 p, 두 번째 문자열 q 위치까지의 LCS 길이

  if string_1 [p] == string_2 [q]:
       dp[p][q] = dp[p-1][q-1] + 1
  else :
       dp[p][q] = max( dp[p-1][q], dp[p][q-1] )

 위 dp 정의에 따라 두 문자열의 dp 배열 저장 값(그림)을 보면 이해하기 쉽다.

 

# Top-down

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
int dp[1001][1001];
string s1, s2;
 
int inline max(int a,int b){return a>b?a:b;}
int solve(int p, int q){
    if(p < 0 || q < 0
        return 0;
    
    int &ret = dp[p][q];
    if(ret != -1){
        return ret;
    }
 
    int h = 0;
    if(p >= 0){
        h = max(h, solve(p-1, q));
    }
 
    if(s1[p] == s2[q]){
        h = max(h, solve(p-1, q-1)+1);
    }
 
    if(q >= 0){
        h = max(h, solve(p, q-1));
    }
 
    return ret = h;
}
cs

 

# Bottom-up

1
2
3
4
5
6
7
8
9
10
11
int dp[1001][1001];
string s1, s2;
 
for(int i=0; i < s1.length(); i++){
    for(int j=0; j < s2.length(); j++){
        dp[i][j] = max( (i==0 ? 0 : dp[i-1][j]), (j==0 ? 0 : dp[i][j-1]));
        if(s1[i] == s2[j]){
            dp[i][j] = max(dp[i][j], (i == 0 || j == 0 ? 0 : dp[i-1][j-1]) + 1);
        }
    }
}
cs

 

 

# 9251 LCS 소스 코드

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<string>
#include<cstring>
 
using namespace std;
int dp[1001][1001];
string s1, s2;
 
int inline max(int a,int b){return a>b?a:b;}
int solve(int p, int q){
    if(p < 0 || q < 0
        return 0;
    
    int &ret = dp[p][q];
    if(ret != -1){
        return ret;
    }
 
    int h = 0;
    if(p >= 0){
        h = max(h, solve(p-1, q));
    }
 
    if(s1[p] == s2[q]){
        h = max(h, solve(p-1, q-1)+1);
    }
 
    if(q >= 0){
        h = max(h, solve(p, q-1));
    }
 
    return ret = h;
}
int main(){
    memset(dp, -1sizeof(dp));
    cin>>s1>>s2;
 
    // cout<< solve(s1.length()-1, s2.length()-1) <<endl;
    // printf("\t");
    // for(int i=0;i<s2.length(); i++){
    //     printf(" %c", s2[i]);
    // }
    // printf("\n");
    for(int i=0; i < s1.length(); i++){
        // printf("%5c\t:", s1[i]);
        for(int j=0; j < s2.length(); j++){
            dp[i][j] = max( (i==0 ? 0 : dp[i-1][j]), (j==0 ? 0 : dp[i][j-1]));
            if(s1[i] == s2[j]){
                dp[i][j] = max(dp[i][j], (i == 0 || j == 0 ? 0 : dp[i-1][j-1]) + 1);
            }
            // printf("%d ", dp[i][j]);
        }
        // printf("\n");
    }
    cout<<dp[s1.length()-1][s2.length()-1<<endl;
    return 0;
}
cs

 


O(N^2), LCS 문자열 출력

LCS 문자열까지 출력하기 위해선 dp 의 마지막 인덱스(dp[s1.length()-1][s2.length()-1]) 부터 0,0 까지 이동해가며, 문자열을 추적해야한다.

 

https://www.acmicpc.net/problem/9252

 

9252번: LCS 2

LCS(Longest Common Subsequence, 최장 공통 부분 수열)문제는 두 수열이 주어졌을 때, 모두의 부분 수열이 되는 수열 중 가장 긴 것을 찾는 문제이다. 예를 들어, ACAYKP와 CAPCAK의 LCS는 ACAK가 된다.

www.acmicpc.net

 

9252 LCS 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
#include<iostream>
#include<string>
#include<cstring>
 
using namespace std;
int dp[1001][1001];
string s1, s2;
 
int inline max(int a,int b){return a>b?a:b;}
int solve(int p, int q){
    if(p < 0 || q < 0
        return 0;
    
    int &ret = dp[p][q];
    if(ret != -1){
        return ret;
    }
 
    int h = 0;
    if(p >= 0){
        h = max(h, solve(p-1, q));
    }
 
    if(s1[p] == s2[q]){
        h = max(h, solve(p-1, q-1)+1);
    }
 
    if(q >= 0){
        h = max(h, solve(p, q-1));
    }
 
    return ret = h;
}
int main(){
    memset(dp, -1sizeof(dp));
    cin>>s1>>s2;
 
    // cout<< solve(s1.length()-1, s2.length()-1) <<endl;
    // printf("\t");
    // for(int i=0;i<s2.length(); i++){
    //     printf(" %c", s2[i]);
    // }
    // printf("\n");
    for(int i=0; i < s1.length(); i++){
        // printf("%5c\t:", s1[i]);
        for(int j=0; j < s2.length(); j++){
            dp[i][j] = max( (i==0 ? 0 : dp[i-1][j]), (j==0 ? 0 : dp[i][j-1]));
            if(s1[i] == s2[j]){
                dp[i][j] = max(dp[i][j], (i == 0 || j == 0 ? 0 : dp[i-1][j-1]) + 1);
            }
            // printf("%d ", dp[i][j]);
        }
        // printf("\n");
    }
    cout<<dp[s1.length()-1][s2.length()-1<<endl;
    int p = s1.length()-1, q = s2.length()-1;
    string ans = "";
    while(p >= 0 && q >= 0){
        if(s1[p] == s2[q]){
            ans = s1[p] + ans;
            p--; q--;
            continue;
        }
        if(p > 0 && dp[p-1][q] == dp[p][q]){
            p--;
        }
        else if(q > 0 && dp[p][q-1== dp[p][q]){
            q--;
        } else {
            break;
        }
    }
    cout << ans << endl;
    return 0;
}
cs

 

 

 


BOJ 1958 LCS 3

https://www.acmicpc.net/problem/1958

 

1958번: LCS 3

첫 줄에는 첫 번째 문자열이, 둘째 줄에는 두 번째 문자열이, 셋째 줄에는 세 번째 문자열이 주어진다. (각 문자열의 길이는 100보다 작거나 같다)

www.acmicpc.net

이 문제는 문자열 3개의 LCS 문제이며, dp를 2차원에서 3차원으로 확장하여 풀면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<iostream>
#include<string>
using namespace std;
string s1,s2,s3;
int dp[101][101][101];
int main(){
    cin >> s1 >> s2 >> s3;
    int s1len = s1.length(), s2len = s2.length(), s3len = s3.length();
    for(int i = 1 ; i <= s1len; i++){
        for(int j = 1 ; j <= s2len; j++){
            for(int p = 1 ; p <= s3len; p ++){
                dp[i][j][p] = max(dp[i-1][j][p], max(dp[i][j-1][p], dp[i][j][p-1]));
                if(s1[i-1== s2[j-1&& s2[j-1== s3[p-1]){
                    dp[i][j][p] = max(dp[i][j][p], dp[i-1][j-1][p-1+ 1);
                }
            }
        }
    }
    cout<< dp[s1len][s2len][s3len] << endl;
    return 0;
}
 
cs

 

 


O(n log n) 방법

https://www.acmicpc.net/problem/13711

 

13711번: LCS 4

LCS(Longest Common Subsequence, 최장 공통 부분 수열)문제는 두 수열이 주어졌을 때, 모두의 부분 수열이 되는 수열 중 가장 긴 것을 찾는 문제이다. 예를 들어, [1, 2, 3]과 [1, 3, 2]의 LCS는 [1, 2] 또는 [1, 3] �

www.acmicpc.net

이 문제는 n 의 길이가 100,000 까지 이므로, 기존의 O(n^2) 방법으로는 풀 수 없다. O(n log n)으로 풀기 위해선 조건이 필요한데, 문제에 쓰여있듯이 하나의 문자는 한 번만 나타나야 한다. 한 번씩 나타날 때, 이 문제를 LIS 로 응용해서 풀 수 있는데, 방법은 다음과 같다.

 

 1. 수열 A 의 값 A[i] 을 인덱스 배열 C에 저장한다. C[ A[i] ] = i
 2. 수열 B의 값을 A 값에 따른 인덱스 배열로 치환한다. B[i] = C[ B[i] ]
 3. B 배열에 LIS 를 구한다.

 

즉, A 값의 인덱스로 B 배열 값을 치환한 뒤, LIS 를 구하면 두 문자열의 LCS가 된다. 

 

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
#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;
 
int n, idx[100001], A[100001], B[100001];
 
int main(){
    scanf("%d"&n);
    for(int i=0;i<n;i++){
        scanf("%d"&A[i]);
        idx[A[i]] = i;
    }
    vector<int> ans;
    for(int i=0;i<n;i++){
        scanf("%d"&B[i]);
        B[i] = idx[B[i]];
        if( ans.empty() || ans.back() < B[i])
            ans.push_back(B[i]);
        else {
            vector<int>::iterator it = lower_bound(ans.begin(), ans.end(), B[i]);
            *it = B[i];
        }
    }
    printf("%d\n", ans.size());
    return 0;
}
cs

 

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

1516 게임 개발  (0) 2020.09.04
BOJ 2407 조합  (0) 2020.09.04
BOJ 가장 긴 증가하는 부분 수열  (0) 2020.09.02
BOJ 4811 알약  (0) 2020.08.28
BOJ 12865 평범한 배낭  (0) 2020.08.28

이 문제 시리즈는 출력 값이 길이만 출력하는지, 긴 수열 또한 출력해야 하는지에 따라 풀이 방법이 달라지고, n 조건에 따라 풀이 방법이 달라진다.

 

1 단계 O(n^2), 가장 긴 수열의 길이만 출력

 

https://www.acmicpc.net/problem/11053

 

11053번: 가장 긴 증가하는 부분 수열

수열 A가 주어졌을 때, 가장 긴 증가하는 부분 수열을 구하는 프로그램을 작성하시오. 예를 들어, 수열 A = {10, 20, 10, 30, 20, 50} 인 경우에 가장 긴 증가하는 부분 수열은 A = {10, 20, 10, 30, 20, 50} 이

www.acmicpc.net

이 문제는 입력 조건 n 의 크기에 따라 풀이 방법이 달라지고, 먼저 11053 번 문제는 n 조건이 1000 까지 이므로 n^2 만에 풀이 가능하며, 이전 배열값을 다시 참조하면서, 가장 긴 값을 저장하면 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<cstdio>
#include<algorithm>
using namespace std;
int n, arr[1001],cnt[1001],ans=1;
int main() {
    scanf("%d"&n);
    for (int i = 0; i < n; i++scanf("%d"&arr[i]);
    for (int i = 0; i < n; i++) {
        cnt[i] = 1;
        for (int j = i - 1; j >= 0; j--) {
            if (arr[i] > arr[j]) {
                cnt[i] = max(cnt[i],cnt[j]+1);
                ans = max(ans, cnt[i]);    
            }
        }
    }
    printf("%d", ans);
    return 0;
}
cs

 

 

2 단계 O( n log n), 가장 긴 수열의 길이만 출력

https://www.acmicpc.net/problem/12015

 

12015번: 가장 긴 증가하는 부분 수열 2

첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000,000)이 주어진다. 둘째 줄에는 수열 A를 이루고 있는 Ai가 주어진다. (1 ≤ Ai ≤ 1,000,000)

www.acmicpc.net

 두 번째 문제는 1 단계 문제와 같이 길이만 출력하지만, n의 조건이 크기 때문에 O(N^2)만에 해결할 수 없다. 따라서 이 문제는 1번 문제 풀이에서 가장 긴 수열의 길이를 탐색해가는 과정을 log 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
#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;
int n, arr[1000001];
 
int main(){
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d"&arr[i]);
    }
 
    vector<int> ans;
    ans.push_back(arr[0]);
    for(int i=1;i<n;i++){
        if(ans.back() < arr[i]){
            ans.push_back( arr[i] );
        } else {
            vector<int>::iterator it = lower_bound(ans.begin(), ans.end(), arr[i]);
            *it = arr[i];
        }
    }
    printf("%d", ans.size());
    return 0;
}
cs

 

 

 

3단계, O(N^2) 수열까지 출력

https://www.acmicpc.net/problem/14002

 

14002번: 가장 긴 증가하는 부분 수열 4

수열 A가 주어졌을 때, 가장 긴 증가하는 부분 수열을 구하는 프로그램을 작성하시오. 예를 들어, 수열 A = {10, 20, 10, 30, 20, 50} 인 경우에 가장 긴 증가하는 부분 수열은 A = {10, 20, 10, 30, 20, 50} 이

www.acmicpc.net

이 문제는 가장 긴 수열의 길이와 더불어 긴 수열까지 모두 출력해야 한다. 위에서 말했듯이 2단계 풀이로는 가장 긴 수열이라고 보장하지 않기 때문에 풀 수 없다. 다행히 이 문제는 다시 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
#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;
int n, arr[1001], len[1001];
 
int main(){
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d"&arr[i]);
    }
 
    vector<int> ans[1001];
    int ans_idx = 0, ans_len = 1;
    for(int i=0;i<n;i++){
        len[i] = 1;
        for(int j=i-1; j >= 0; j--){
            if(arr[i] > arr[j] && len[i] < len[j] + 1){
                len[i] = len[j] + 1;
                ans[i].clear();
                ans[i].insert(ans[i].end(), ans[j].begin(), ans[j].end());
                if(ans_len < len[i]){
                    ans_len = len[i];
                    ans_idx = i;
                }
            }
        }
        ans[i].push_back(arr[i]);
    }
    printf("%d\n", ans_len);
    for(int i=0;i<ans_len;i++){
        printf("%d ",ans[ans_idx][i]);
    }
    return 0;
}
cs

 

 

 

4단계 O(n log n), 수열까지 출력

https://www.acmicpc.net/problem/14003

 

14003번: 가장 긴 증가하는 부분 수열 5

첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000,000)이 주어진다. 둘째 줄에는 수열 A를 이루고 있는 Ai가 주어진다. (-1,000,000,000 ≤ Ai ≤ 1,000,000,000)

www.acmicpc.net

마지막은 n 조건도 커지고, 정답 수열까지 출력해야 하는 문제이다. 이 문제를 풀기 위해선 2번째 문제 풀이를 응용하여 lower_bound(이진탐색)으로 찾은 인덱스 값을 따로 저장해 풀 수 있다. 마지막 출력 시 뒤에서 부터 정답 수열 길이 값과 매칭되는 인덱스 값을 순차적으로 찾으며 출력하면 된다.

 

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
#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;
int n, arr[1000001], idx[1000001], ans2[1000001];
 
int main(){
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d"&arr[i]);
    }
 
    vector<int> ans;
    ans.push_back(arr[0]);
    idx[0= 0;
    for(int i=1; i<n; i++){
        if(ans.back() < arr[i]) {
            ans.push_back(arr[i]);
            idx[i] = ans.size()-1;
        } else {
            vector<int>::iterator it = lower_bound(ans.begin(), ans.end(), arr[i]);
            *it = arr[i];
            idx[i] = distance(ans.begin(), it);
        }
    }
 
    printf("%d\n", ans.size());
    for(int i = n-1, len = ans.size()-1; i>=0 && len>=0; i--){
        if(idx[i] == len){
            ans2[len] = arr[i];
            len --;
        }
    }
 
    for(int i=0; i < ans.size(); i++){
        printf("%d ", ans2[i]);
    }
    return 0;
}
cs

 

결국 4단계 풀이 방법만 기억하고 있으면, 모두 풀 수 있다.

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

BOJ 2407 조합  (0) 2020.09.04
LCS 최장 공통 부분 수열  (0) 2020.09.03
BOJ 4811 알약  (0) 2020.08.28
BOJ 12865 평범한 배낭  (0) 2020.08.28
BOJ 15649~15650 N과 M  (0) 2020.08.20

 

 

 

이 문제는 동적계획법(DP)으로 풀 수 있는 문제이다. 그 이유는 완전한 알약 갯수와 반쪽 짜리 알약 갯수가 주어질 때, 문자열의 경우의 수는 일정하기 때문이다. 따라서 DP는 다음과 같이 정의 할  수 있다.

dp[w][h] = 완전한 알약이 w 개 있고,  반쪽 짜리 알약이 h 개 있을 때, 문자열의 경우의 수

dp[w][h] = dp[w-1][h+1]  +  dp[w][h-1]

 

 

 

Top-Down

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef long long ll;
ll dp[33][66];
 
ll solve(int w, int h){
    if(w == 0)
        return 1;
    
    ll& ret = dp[w][h];
    if(ret != 0)
        return ret;
    
    ll s = solve(w-1, h+1);
    if(h > 0)
        s = s + solve(w, h-1);
    return ret = s;
}
cs

 

 

Bottom-Up

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef long long ll;
ll dp[33][66];
 
// 초기화    
for(int h=1; h<=60;h++)
    dp[0][h] = 1;
dp[1][0= 1;
 
for(int w=1; w<=30; w++){
    for(int h=0; h<=60; h++){
        dp[w][h] = dp[w-1][h+1+ (h > 0 ? dp[w][h-1] : 0);
    }
}
cs

이 문제는 초기화만 잘 해준다면 Top-Down 방식과 코드가 큰 차이가 없이 Bottom-Up 또한 꽤 직관적인 코드가 짜여진다.

 

 

 

전체 소스 코드

 

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
#include<stdio.h>
 
int n;
typedef long long ll;
ll dp[33][66];
ll inline  max(ll a, ll b){return a>b?a:b;}
ll solve(int w, int h){
    if(w == 0)
        return 1;
    
    ll& ret = dp[w][h];
    if(ret != 0)
        return ret;
    
    ll s = solve(w-1, h+1);
    if(h > 0)
        s = s + solve(w, h-1);
    return ret = s;
}
int main(){
    // solve(30, 0);
    
    for(int h=1; h<=60;h++)
        dp[0][h] = 1;
    
    dp[1][0= 1;
    for(int w=1; w<=30; w++){
        for(int h=0; h<=60; h++){
            dp[w][h] = dp[w-1][h+1+ (h > 0 ? dp[w][h-1] : 0);
        }
    }
 
    while(1){
        scanf("%d",&n);
        if(n==0break;
        printf("%lld\n", dp[n][0]);
    }
    return 0;
}
cs

 

 

 

 

 

 

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

LCS 최장 공통 부분 수열  (0) 2020.09.03
BOJ 가장 긴 증가하는 부분 수열  (0) 2020.09.02
BOJ 12865 평범한 배낭  (0) 2020.08.28
BOJ 15649~15650 N과 M  (0) 2020.08.20
BOJ 2580 스도쿠  (0) 2020.08.20

 

이 문제는 다이나믹 프로그래밍(동적계획법, DP) 문제이다. 나는 평소에 Top-down 방식으로 구현하는 것이 익숙해, bottom-up 방식의 구현을 잘 구현하지 못한다.. 그래서 bottom-up 방식으로도 구현하는 연습해야겠다고 생각해서 오랜만에 DP 문제를 찾았다.

 

DP 는 큰 문제를 작은 문제로 나누어푸는데, 분할정복과 다른 점은 DP는 작은 문제로 나누어 풀 때, 작은 문제들의 결과가 항상 동일하고 반복이 일어난다. 작은 문제들의 일정한 결과를 이용해 큰 문제를 해결하는 과정이라고 볼 수 있다. 따라서, 작은 문제들의 일정한 결과 값을 저장하기 위해 Memoization 을 하며, 같은 작은 문제를 만났을 때 이미 한 번 해결했던 문제라면 저장해놓은 값을 이용해 반복 수행 횟수를 줄일 수 있다.

 

Top-Down

 

먼저  Top-Down 방식의 풀이에서는, DP를 다음과 같이 정의한다.

dp[p][q] = q 만큼의 무게를 들 수 있을 때,  p 번째의 물건을 선택해 얻을 수 있는 최대의 가치

이렇게 정의하면, 

dp[p][q] = max( dp[p-1][q] ,  dp[p-1][ q - weight[p] ] + value[p] )

처럼 p 번째 물건을 선택하지 않은 경우와 선택한 경우 모두 고려할 수 있다.

 

기저조건(재귀의 탈출조건) 은 p 번째 물건이 0~n-1 번의 물건 개수 범위를 넘어설 때이다. Top-down 방식의 구현은 다음과 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
int solve(int p, int q){
    if(p == -1return 0;
 
    int& ret = dp[p][q];
    if(ret != 0)
        return ret;
    
    int h = solve(p-1, q);
    if(q - arr[p][0>= 0)
        h = max( h, solve(p-1, q-arr[p][0]) + arr[p][1] );
 
    return ret = h;
}
cs

 

 

Bottom-Up

 

다음은 반복문을 이용해 구현하는 Bottom-Up 방법으로 풀어보면, 마찬가지로 2차원의 배열이 필요하다. 익숙치 않아서 시간이 꽤 걸렸다.

DP의 정의는 같으며, 내가 구현한 방식은 다음과 같다.

 

1
2
3
4
5
6
7
8
9
10
    for(int i=1;i<=n;i++){
        dp[i][arr[i-1][0]] = arr[i-1][1];
        for(int w=0; w<=k; w++){
            dp[i][w] = dp[i-1][w];
            if(w - arr[i-1][0>= 0){
                dp[i][w] = max(dp[i][w], dp[i-1][w-arr[i-1][0]] + arr[i-1][1]);
            }
        }
    }
    printf("%d\n", dp[n][k]);
cs

 

위 문제의 예제는 너무 쉽기 때문에, 다음 예제를 이용해 설명하면 다음과 같다.

4 20
6 13
2 5
3 9
10 20

4개의 물건이 있으며 무게는 7 까지 가능하기 때문에, 배열은 dp[4][7] 크기가 필요하다.

dp 에는 i 번째 물건까지 고려했을 때, w 무게에서의 최대 가치를 저장한다. 

즉,

 

무게는 0~20 까지 모두 출력되었다.  dp[1] 은 첫 번째 물건만을 고려했을 때 까지의 가능한 최대 가치를 보여주며, dp[2]는 두 번째 물건 까지 고려했을 때의 가치이다. 두 번째 물건의 무게는 2로 인덱스 2번 부터 무게 5가 채워지며, 무게 6부터는  첫 번째 물건의 가치가 더 크기 때문에 13이 유지되며, 두 무게를 합친 8 무게부터는 두 가치를 합친 값이 저장되기 시작한다. 

 

 

전체 소스 코드

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<stdio.h>
 
int n,k;
int arr[101][2], dp[101][100001];
 
int inline max(int a,int b){ return a>b? a:b;}
int solve(int p, int q){
    if(p == -1return 0;
 
    int& ret = dp[p][q];
    if(ret != 0)
        return ret;
    
    int h = solve(p-1, q);
    if(q - arr[p][0>= 0)
        h = max( h, solve(p-1, q-arr[p][0]) + arr[p][1] );
 
    return ret = h;
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++){
        scanf("%d %d",&arr[i][0], &arr[i][1]);
    }
 
    // int ans = solve(n-1, k);
    // printf("%d\n", ans);
 
    for(int i=1;i<=n;i++){
        dp[i][arr[i-1][0]] = arr[i-1][1];
        // printf("dp[%d] : ",i);
        for(int w=0; w<=k; w++){
            dp[i][w] = dp[i-1][w];
            if(w - arr[i-1][0>= 0){
                dp[i][w] = max(dp[i][w], dp[i-1][w-arr[i-1][0]] + arr[i-1][1]);
            }
            // printf("%2d ", dp[i][w]);
        }
        // printf("\n");
    }
    printf("%d\n", dp[n][k]);
 
    return 0;
}
 
 
cs

 

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

BOJ 가장 긴 증가하는 부분 수열  (0) 2020.09.02
BOJ 4811 알약  (0) 2020.08.28
BOJ 15649~15650 N과 M  (0) 2020.08.20
BOJ 2580 스도쿠  (0) 2020.08.20
BOJ 1194 달이 차오른다, 가자.  (0) 2020.08.11

N과 M 수열 문제 시리즈를 하나의 포스트에 정리하려 한다.

수열을 출력하는데, 중복 숫자의 뒤집힌 수열을 포함하는지에 따라 구현 방법이 달라진다.

 

BOJ 15649 N과 M (1)

자연수 N과 M이 주어졌을 때, 아래 조건을 만족하는 길이가 M인 수열을 모두 구하는 프로그램을 작성하시오.

  • 1부터 N까지 자연수 중에서 중복 없이 M개를 고른 수열

 

 

이 문제는 같은 숫자의 뒤집힌 수열을 포함한다(ex: 1 4,  4 1)

재귀함수를 이용해 백트래킹으로 1~N 까지 숫자를 순차적으로 M개 출력하여 풀 수 있다.

 

N과 M(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
31
#include<stdio.h>
#include<vector>
using namespace std;
 
int n,m;
bool nums[10];
void solve(vector<int>& v){
    if(v.size() == m) {
        for(int i=0;i<v.size();i++){
            printf("%d ",v[i]);
        }
        printf("\n");
        return;
    }
 
    for(int i=1;i<=n;i++){
        if(!nums[i]){
            v.push_back(i);
            nums[i] = 1;
            solve(v);
            nums[i] = 0;
            v.pop_back();
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    vector<int>v;
    solve(v);
    return 0;
}
cs

 

 

 

 

BOJ 15650 N과 M (2)

자연수 N과 M이 주어졌을 때, 아래 조건을 만족하는 길이가 M인 수열을 모두 구하는 프로그램을 작성하시오.

  • 1부터 N까지 자연수 중에서 중복 없이 M개를 고른 수열
  • 고른 수열은 오름차순이어야 한다.

 

 

이 문제는 같은 숫자의 뒤집힌 수열을 포함하지 않는다. 한 번 나온 숫자의 조합은 한 번만 출력된다.

이런 문제는 순열로 풀 수 있다.

1
2
3
4
5
6
#include<algorithm>
 
std::vector<bool> v;   // 0 1 0 1
...
std::prev_permutation( v.begin(), v.end() ); // 0 0 1 1
std::next_permutation( v.begin(), v.end() ); // 0 1 1 0
cs

 

 

 

N과 M (2) 소스코드

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;
 
int n,m;
 
int main(){
    scanf("%d%d",&n,&m);
    
    vector<bool> v(n);
    fill(v.begin(), v.begin()+m, 1);
    
    do {
        for(int i=0; i<v.size(); i++){
            if(v[i])
                printf("%d ",i+1);
        }
        printf("\n");
    } while( prev_permutation(v.begin(), v.end()) );
    return 0;
}
cs

 

 

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

BOJ 4811 알약  (0) 2020.08.28
BOJ 12865 평범한 배낭  (0) 2020.08.28
BOJ 2580 스도쿠  (0) 2020.08.20
BOJ 1194 달이 차오른다, 가자.  (0) 2020.08.11
BOJ 6087 레이저통신  (0) 2020.08.09

+ Recent posts