알고리즘/알고리즘 노트

[다이나믹]-문제3

산을좋아한라쯔 2016. 7. 30. 20:24
반응형

1. 행렬곱 순서

2.  prime - 최대공약수가 1인 수열 개수 

3. 가장 긴 증가하는 부분 수열

4. 교차하지 않는 원의 현들의 최대집합 (백준 2673)

5. 식당 (백준 6101)

6. 김치배달(백준2184)

7. 1,2,3 더하기 (백준 9095)

8. 동전 바꿔주기 (백준 2624)

9. 내리막 길 (백준 1520)


-------------------------------

1. 행렬곱 순서


풀이.

DP로 풀어야 한다.


D[i][j]: i번째 행렬에서부터 j번째 행렬까지의 최소 곱

D[i][j] = min(D[i][k] + D[k+1][j] + P[i-1] * P[k] * P[j]), i<= k <j

D[i][j] = 0 , when i == j


위 식의 나오게 된 배경은,

행렬 A1, A2, ... An 이 있고, 

A1 = p0 x p1, A2=p1 x p2, ... An=pn-1 x pn 이라할 때,

(A1~Ak)과 (Ak+1 ~ An)까지의 곱으로 나눠서 생각할 수 있고, 각각의 최소값을 구한다는 개념.

이 때 두 그룹의 각각 최소값인 D[i][k]와 D[k+1][j]를 더하고, 또한 두 그룹을 곱하면서 나오는 연산횟수인 P[i-1] * P[k] * P[j]를 더해서 최솟값을 구해야 함.(행렬 P0 x P1 과 P1 x P2를 곱하면, P0 x P1 x P2 만큼의 연산이 발생)


위 점화식을 다이나믹 프로그래밍 해야는데, 코드는 아래와 같다.

         for (int r = 1; r <= (N - 1); ++r) {

                  for (int i = 1; i <= (N - r); ++i) { 

                           int j = i + r;

                           int low = INT_MAX;

                           int minK;

                           for (int k = i; k < j; ++k) {

                                   int tmp = D[i][k] + D[k + 1][j] + P[i - 1] * P[k] * P[j];

                                   if (tmp < low) {

                                            low = tmp;

                                            minK = k;

                                   }

                           }

                           D[i][j] = low;

                           T[i][j] = minK;                   

                  }

         }


위 코드에서, r값을 변화시키는 것에 주의. 이 r값은, 몇 개만큼의 행렬을 묶어서 계산해보느냐를 나타냄. 즉, r이 1이면, (A1 x A2), (A2 x A3), (A3 x A4) ...처럼 해보는 것이고 (i를 증가시키면서)

r=2이면, (A1 x A2 x A3), (A2 x A3 x A4) ... 이렇게 해보는 것.


중간에 minK값을 보관한 것은, 행렬들의 곱 순서를 보관하기 위해서임. 이 테이블 T를 보고서, 실제 행렬곱 순서를 정할 수 있음.

예를 들어 행렬 A1, A2, A3 가 있을 때, 계산결과 T테이블 값이 다음과 같이 도출되었다 하자.

T[1][2] = 1, T[2][3] = 2, T[1][3] = 2  나머지는 갱신안되어 zero


이것은 (A1A2)A3 로 계산되어야한다는 것을 의미한다. 

T[1][2]=1이므로 그냥 A1A2가 계산. T[2][3]=2이므로 A2다음에 끊는다는 얘기므로 그대로 A2A3.

T[1][3]=2은 A1~A3까지의 곱에서 A2다음에 끊는다는 얘기므로, (A1A2)A3


이러한 곱셈순서를 괄호로 프린트하려면 다음과 같이 하면 된다. (재귀호출. 잘 외두두기 바람)

void print_array(int i, int j) {

        if (i == j) printf("A%d", i);

        else {

               printf("(");

               print_array(i, T[i][j]);

               print_array(T[i][j] + 1, j);

               printf(")");

        }

}


또한 이 T 테이블을 이용해서, 실제 곱셈을 효율적인 순서대로 할 수 있다. 

vector<vector<int> > doMulti(vector<vector<int> > a, vector<vector<int> > b) {

         int p, q, r;

         p = a.size();

         q = b.size();

         r = b[0].size();

 

         vector<vector<int> > c(p,vector<int>(r,0));

         for (int i = 0; i < p; ++i) {

                  for (int j = 0; j < r; ++j) {

                           c[i][j] = 0;

                           for (int k = 0; k < q; ++k) {

                                   c[i][j] += a[i][k] * b[k][j];

                           }

                  }

         }

         return c;

}

 

vector<vector<int> > multi(int i, int j) {

         if (i == j) return v[i];

         return doMulti(multi(i,T[i][j]), multi(T[i][j]+1,j));

}

 

전체 소스.

#include <stdio.h>

#include <limits.h>

#include <vector>

#include <algorithm>

 

#define MAX 503

#define min(a,b) ((a) < (b) ? (a) : (b))

using namespace std;

 

struct xy {

         int x, y;

         xy(int  _x, int _y) { x = _x; y = _y; }

};

typedef pair<xy, int> xyi;

 

int N;

int P[MAX + 5];

int D[MAX][MAX];

int T[MAX][MAX];

 

vector<vector<int> > v[MAX];

void print_array(int i, int j) {

         if (i == j) printf("A%d", i);

         else {

                  printf("(");

                  print_array(i, T[i][j]);

                  print_array(T[i][j] + 1, j);

                  printf(")");

         }

}

 

vector<vector<int> > doMulti(vector<vector<int> > a, vector<vector<int> > b) {

         int p, q, r;

         p = a.size();

         q = b.size();

         r = b[0].size();

 

         vector<vector<int> > c(p,vector<int>(r,0));

         for (int i = 0; i < p; ++i) {

                  for (int j = 0; j < r; ++j) {

                           c[i][j] = 0;

                           for (int k = 0; k < q; ++k) {

                                   c[i][j] += a[i][k] * b[k][j];

                           }

                  }

         }

         return c;

}

 

vector<vector<int> > multi(int i, int j) {

         if (i == j) return v[i];

         return doMulti(multi(i,T[i][j]), multi(T[i][j]+1,j));

}

 

int main() {

         freopen("행렬곱.txt", "r", stdin);

 

         scanf("%d", &N);

         for (int i = 0; i <= N; ++i) {

                  scanf("%d %d", &P[i], &P[i + 1]); //주의

         }

 

         for (int r = 1; r <= (N - 1); ++r) {

                  for (int i = 1; i <= (N - r); ++i) {

                           int j = i + r;

                           int low = INT_MAX;

                           int minK;

                           for (int k = i; k < j; ++k) {

                                   int tmp = D[i][k] + D[k + 1][j] + P[i - 1] * P[k] * P[j];

                                   if (tmp < low) {

                                            low = tmp;

                                            minK = k;

                                   }

                           }

                           D[i][j] = low;

                           T[i][j] = minK;                   

                  }

         }

 

         printf("%d\n", D[1][N]);

                 

         print_array(1, N);

 

 

 

         return 0;

}


2. prime 

어떤 수열 S가 주어진다. 수열 S에서 한 개 이상 원소를 선택했을 때, 선택한 수 혹은 수들의 최대공약수가 1이 되는 것의 개수를 구하는 프로그램을 작성하시오.


입력

첫 번째 줄에 수열의 크기 N이 주어진다. (1 ≤ N ≤ 100)


두 번째 줄부터 N개의 줄에 걸쳐 수열의 각 원소 Si가 주어진다. 어떤 두 원소가 같을 수 있다. (1 ≤ Si ≤ 100,000)


출력

첫 번째 줄에 정답을 10,000,003으로 나눈 나머지를 출력한다.


힌트

입력 예제


3

2

4

3

출력 예제


3

예제 설명


주어진 수를 이용해서 선택하는 모든 방법을 나열하면 아래와 같다.


{2} : 최대공약수는 2이다.

{3} : 최대공약수는 3이다.

{4} : 최대공약수는 4이다.

{2, 3} : 최대공약수는 1이고, 답이 되는 경우 중 한 가지가 된다.

{2, 4} : 최대공약수는 2이다.

{3, 4} : 최대공약수는 1이고, 답이 되는 경우 중 한 가지가 된다.

{2, 3, 4} : 최대공약수는 1이고, 답이 되는 경우 중 한 가지가 된다.

즉, 총 3가지의 경우가 존재한다.


풀이

....어려움


소스

#include <stdio.h>

#include <algorithm>

#include <vector>

using namespace std;

#define M 10000003

 

vector<int> v[100002];

int n, s[102], d[102][100002], ans;

int gcd(int a, int b) {

         if (a > b) return gcd(b, a);

         if (b%a == 0) return a;

         else return gcd(b%a, a);

}

int main() {

         freopen("prime.txt", "r", stdin);

 

         int i, j, k;

         scanf("%d", &n);

         for (i = 1; i <= n; i++) scanf("%d", &s[i]);

         sort(s + 1, s + n + 1);

 

         // 수에 대해 약수 구하기  

         for (i = 1; i <= s[n]; i++) {

                  for (j = 1; j*j <= i; j++) {

                           if (i%j == 0) {

                                   v[i].push_back(j);

                                   if (j*j != i) v[i].push_back(i / j);

                           }

                  }

         }

 

         //d[i][j]: 1번째 수에 대해, 1~i까지의 gcd=j 갯수

         for (i = 1; i <= n; i++) {

                  d[i][s[i]] = 1; //자기자신

                  for (j = 1; j<i; j++) {

                           for (k = 0; k<v[s[j]].size(); k++) {

                                   int t = v[s[j]][k];

                                   if (gcd(t, s[i]) == 1) {

                                            d[i][1] += d[j][t]; //핵심

                                            d[i][1] %= M;

                                   }

                                   else d[i][gcd(t, s[i])] += d[j][t], d[i][gcd(t, s[i])] %= M;

                           }

                  }

                  ans += d[i][1]; ans %= M;

         }

         printf("%d", ans);

         return 0;

}


3. 가장 긴 증가하는 부분 수열 (백준 11053번)

수열 A가 주어졌을 때, 가장 긴 증가하는 부분 수열을 구하는 프로그램을 작성하시오.


예를 들어, 수열 A = {10, 20, 10, 30, 20, 50} 인 경우에 가장 긴 증가하는 부분 수열은 A = {10, 20, 10, 30, 20, 50} 이고, 길이는 4이다.


입력

첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000)이 주어진다.


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


출력

첫째 줄에 수열 A의 가장 긴 증가하는 부분 수열의 길이를 출력한다.


예제 입력  

6

10 20 10 30 20 50


예제 출력

4


핵심

- i번째에 대한 DP를 세우는 것이 아니고, 수열값 1~1000에 대해서 DP세워야 함

- D[i] : i 값에 대해 0~i 까지에 대한 최대 증가 수열 크기. ex)D[4]: 값 4에 대한 최대증가수열 크기

- D[i] = max(D[0]~D[3]) + 1

- D[A[1]] = 1 


소스

#include <stdio.h>

 

int N;

int A[1001], D[1001];

int main() {

         scanf("%d", &N);

         for (int i = 1; i <= N; ++i) scanf("%d", &A[i]);

 

         int max=1;

         D[A[1]] = 1;

         for (int i = 2; i <= N; ++i) {

                  int M = 0;

                  for (int j = 0; j <= A[i]-1; ++j) if (D[j] > M) M = D[j];                              

                  D[A[i]] = M + 1;

                  if (max < D[A[i]]) max = D[A[i]];

         }

         printf("%d\n", max);

         return 0;

}


4. 교차하지 않는 원의 현들의 최대집합 (백준 2673)

평면상에 있는 원의 둘레에 100개의 점이 일정한 간격으로 시계방향으로 번호가 1, 2, ... 100으로붙여져 있다. 이 점들을 끝점으로 갖는 N개의 선분(원의 현)이 입력으로 주어질 때, 이들중에서 서로 교차하지 않는 것들을 최대한 많이 찾아서 그 개수를 출력하는 프로그램을 작성하라.


단, 1≤N≤50이고, 주어진 각 점은 많아야 한 현의 끝점이 될 수 있다. 


입력

첫번째 줄은 주어지는 현의 개수 N이고, 다음의 N줄은 각 현의 양끝점의 번호가 주어진다.


출력

구한 현의 개수를 출력한다.


예제 입력  

5

97 31

1 45

27 5

11 65

43 72


예제 출력 

3




풀이

- 1~100까지의 수직선상에서, 서로 겹치지않게 영역을 지정하는 문제와 동일 (1과 100사이를 칼로 잘라 선분을 만들어서 생각)

- D[i][j]: 점 i~j까지에서의 겹치지않는 현 최대 갯수

- D[i][j] = Max(D[i][k-1] + D[k+1][j-1] + q[k][j]), i<=k<=j

  q[k][j]: 점 k와 j를 잇는 선분이 있으면 1, 없으면 0

-최적행렬곱에 대한 DP풀이 처럼, i~j사이를 1,2,3씩 증가시키면서 계산




소스

#include <stdio.h>

#define max(a,b) ((a) > (b)) ? (a) : (b)

 

int N;

int q[101][101]; //q[i][j]: 수직선상의 i에서 j로의 선이 있으면 1, 없으면 0

int D[101][101]; //D[i][j]: i에서 j까지 범위에서 가질 있는 중보없는 최대 갯수. D[1][100]=?

//D[i][j] = Max(D[i][k-1] + D[k+1][j-1] + q[k][j]) i <= k <= j

int main() {

         scanf("%d", &N);

         int s,e;

         for (int i = 1; i <= N; i++) {

                  scanf("%d%d", &s, &e);

                  if (s < e) q[s][e] = 1;

                  else q[e][s] = 1;

         }

 

         for (int d = 1; d <= 99; ++d) { //i j 사잇 길이: 1~99

                  for (int i = 1; i <= (100 - d); ++i) {

                           int j = i + d;

                           for (int k = i; k <= j; ++k) D[i][j] = max(D[i][j], D[i][k - 1] + D[k + 1][j - 1] + q[k][j]);                     

                  }

         }

         printf("%d\n", D[1][100]);

         return 0;

}


5. 식당 (백준 6101)

농부 존의 식당은 N마리의 소들에게 M개의 음식을 제공해 주고 있다.


소들은 자신들이 선호하는 음식 Pi를 가지고 있는데, 농부 존은 다음 방법으로 소들에게 음식을 공급한다.


들어오는 소들을 순서대로 그룹으로 나눈다. [1 ~ 4] / [5 ~ 7] / [8 ~ 10] 이런 식으로.

그룹에 있는 소들에게 음식을 제공하는 데 드는 비용은 (해당 그룹에서 선호하는 음식의 합집합 크기)^2 이다. 음식을 수로 생각하면, 서로 다른 수들의 개수의 제곱이다.

최소 비용으로 음식을 제공하려면 어떻게 해야 할까?


입력

첫번째 줄에 N, M이 주어진다. (1 <= M <= N <= 40000)


이후 N개의 줄에 Pi가 주어진다. (1 <= Pi <= M)


출력

최소 비용을 출력하라.


예제 입력  복사

13 4

1

2

1

3

2

2

3

4

3

4

3

1

4


예제 출력

11


힌트

[1] [2] [3] [4] [5, 6] [7, 8, 9, 10, 11] [12] [13] 과 같이 묶으면, 1 + 1 + 1 + 1 + 1 + 4 + 1 + 1 = 11의 비용이 든다.


풀이

D[i]: i부터 N까지 최소비용

D[N]=1, D[1]=?

B(i,j) : i에서 j까지를 한 박스에 담았을 때 비용

D[i] = Min(B(i,i+j)+D[i+j+1]), 0 <= j <= N-i

        where, B(i,i+j) < (N-i+1) 

                if(cnt == M) D[i] = min(D[i], cnt * cnt)


- 핵심은 B(i,i+k)를 상수시간에 찾을 수 있어야 함. (아래 소스 참조)

  또한, j를 증가시키며(박스에 담는 수를 증가시키며) min값을 찾을 때, B(i,i+j) >= (N-i+1) 가되면 더 이상 박스에 담을 필요 없음.

  그리고, 한박스내 가짓수를 나타내는 cnt가 전체 가짓수 M과 같아지면, 하나씩 j를 증가시켜봐야 의미없고, 모두 한 박스에 넣었을 때랑 비교


소스

#include <stdio.h>

#define MAX 987654321

int N, M, i,j, flag = 0, square, cnt;;

int p[40001], D[40002], T[40002];

int main() {

         scanf("%d%d", &N, &M);

         for (i = 1; i <= N; ++i) scanf("%d", &p[i]);

         D[N] = 1;        

         for (i = N - 1; i >= 1; --i) {

                  ++flag; cnt = 0; D[i] = MAX;

                  for (j = 0; j <= (N - i); ++j) {

                           if (T[p[i + j]] != flag) {

                                   cnt++; square = cnt * cnt;

                                   T[p[i + j]] = flag;

 

                                   if ((square) >= (N - i + 1)) break;

                                   if (cnt == M) {

                                            if (square < D[i]) D[i] = square;  break;

                                   }

                           }

                           if ((square + D[i + 1 + j]) < D[i]) D[i] = square + D[i + 1 + j];

                  }

         }

         printf("%d\n",D[1]);

         return 0;

}

 

 6. 김치 배달(백준 2184번)

월드 식품에서는 김치를 만들어 여러 도시들에 배달 판매하는 일을 하고 있다. 각각의 도시들과 김치 공장은 1차원 직선상의 점에 위치해 있다. 각 도시는 정수 좌표로 나타난다.


배달을 할 때에는 공장에서 N(1≤N≤1,000)포기의 김치를 들고 시작한다. 그리고 1차원 직선을 따라 왼쪽이나 오른쪽으로 움직인다. 이동을 할 때에는 1초에 한 칸씩 움직일 수 있다. 또한 어떤 도시에 도착했을 때 김치는 0의 시간에 배달되는 것으로 생각한다. 즉 도시에 도착하기만 하면 배달이 완료되는 것으로 생각한다. 또한 김치를 배달하는 순서는 상관이 없다.


각각의 김치는 모두 t=0 의 시각에 공장에서 출발된다. 각각의 김치는 1초에 1만큼씩 쉬게 되는데, 김치가 쉬게 될 경우 소비자가 불만을 토로할 수 있다. 따라서 월드 식품에서는 각 도시에 김치가 도착했을 때의 김치의 쉰 정도의 합을 최소로 하려 한다.


각 도시의 위치 및 김치 공장의 위치(x좌표)가 주어졌을 때, 모든 도시에 김치를 배달할 때의 김치의 쉰 정도의 합의 최소값을 구하는 프로그램을 작성하시오.


입력

첫째 줄에 두 정수 N, L이 주어진다. L은 김치 공장의 x좌표이다. 다음 N개의 줄에는 김치를 배달할 도시의 x좌표가 주어진다. 모든 좌표는 1이상 1,000,000이하의 정수이다.


출력

첫째 줄에 답을 출력한다.


예제 입력  복사

4 10

1

9

11

19


풀이

D[i][j][k]: i번째 도시에서 j번째 도시까지 배달완료하고, k(0:left, 1:right) 위치일때, 남아있는 도시배달 최솟값

D[0][N][k] = 0, D[S][S][0]=?

D[i][j][k][r] =

  cur = (k==0) ? p[i] : p[j]; 

  min(D[i-1][j][0][r-1] + d(cur,p[i-1]) * r, D[i][j+1][1][r-1] + d(cur,p[j+1]) * r


- 출발점까지 넣어서 소팅을 한 후, 출발점에 대한 인덱스는 이분탐색으로 찾는다. (N이 1000이므로, 이분탐색 안해도 무방)

- 점화식을 세울 때, 처리된 비용 최솟값이 아닌, 남아 있는 최솟값으로 해서 식을 세우는 것이 핵심.

- 일직선상이기에 다음 사항이 자명한 것을 이용

   . 종료하는 지점은, 왼쪽 끝이거나, 오른 쪽 끝

   . 처음 시작점에서 출발하여 왼쪽 혹은 오른쪽으로 진행. 왼쪽으로는 0이될때까지, 오른쪽으로는 N까지 진행(전체 N번 진행됨: 0~N까지의 선분 수)

- 김치가 쉬는 정도는 누적되어 계산되기에, D[S][S][0]와 D[S-1][S][0]와의 차이는 d(S-1,S) * N 

   --> 즉, 처음 거리는 N번 영향을 줌. 이것을 반영하기 위해, 변수 r의 처음값을 N으로 두고, 재귀함수를 콜할 때 하나씩 낮춰 줌


소스

#include <stdio.h>
#include <algorithm>
 
#define d(a,b) ( ((a)>(b)) ? ((a)-(b)) : ((b)-(a)))
#define min(a,b) (((a)<(b)) ? (a) : (b))
#define MAX 987654321
 
using namespace std;
 
int N, L, S; //S: L's index after sorting
int p[1003];
int D[1003][1003][2];
 
/*
D[i][j][k]: i번째 도시에서 j번째 도시까지 배달완료하고, k(0:left, 1:right) 위치일때, 남아있는 도시배달 최솟값
D[0][N][k] = 0, D[S][S][0]=?
D[i][j][k][r] =
  cur = (k==0) ? p[i] : p[j];
  min(D[i-1][j][0][r-1] + d(cur,p[i-1]) * r, D[i][j+1][1][r-1] + d(cur,p[j+1]) * r
*/
//i:left, j:right, k:flag, r:remained
int solve(int i,int j, int k, int r){
    if (i == 0 && j == N) return 0;
    if (D[i][j][k]) return D[i][j][k];
    D[i][j][k] = MAX;
 
    int cur = (k == 0) ? p[i] : p[j];
    if (i > 0) D[i][j][k] = solve(i - 1, j, 0, r - 1) + d(cur,p[i-1]) * r;
    if (j < N) D[i][j][k] = min(D[i][j][k], solve(i,j+1,1,r-1) + d(cur,p[j+1]) * r);
 
    return D[i][j][k];
}
 
 
int findIdx(int s, int e){
    int m;
    while (s <= e){
        m = (s + e) / 2;
        if (p[m] == L) return m;
        if (p[m] < L) s = m + 1;
        else e = m - 1;
    }
    return s;
}
 
int main(){
    //freopen("김치배달.txt", "r", stdin);
    scanf("%d%d", &N, &L);
    p[0] = L;
    for (int i = 1; i <= N; ++i) scanf("%d", &p[i]);
    sort(p, p + N + 1);
    S = findIdx(0, N);
 
    printf("%d\n", solve(S,S,0,N));
}


7. 1,2,3 더하기 (백준 9095)

정수 4를 1, 2, 3의 조합으로 나타내는 방법은 총 7가지가 있다.


1+1+1+1

1+1+2

1+2+1

2+1+1

2+2

1+3

3+1

정수 n이 주어졌을 때, n을 1,2,3의 합으로 나타내는 방법의 수를 구하는 프로그램을 작성하시오.


입력

첫쨰 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스는 한 줄로 이루어져 있고, 정수 n이 주어진다. n은 양수이며 11보다 작다.


출력

각 테스트 케이스마다, n을 1,2,3의 합으로 나타내는 방법의 수를 출력한다.


예제 입력  

3

4

7

10


예제 출력  

7

44

274


풀이

D[i]: 숫자 i에 대한 1,2,3의 덧셈 조합으로 만들 수 있는 갯수

D[1]=1, D[2]=2 (1+1,2), D[3]=4 (1+1+1, 1+2, 2+1, 3)

D[4]를 고민해봤을 때, 각 조합을 이렇게 생각할 수 있다. 

  - 1을 제일먼저 놨을 때, 2를 제일 먼저 놨을 때, 3을 제일 먼저 놨을 때. 

    1을 제일먼저 놓으면 뒷 부분의 합은 '3'이 되야되는 조합임. 즉,

    1 + {합이 3이되는 조합} = 1 + (1+1+1), 1+(1+2), 1+(3) -> D[3] = 4

    2 + {합이 2가되는 조합} = 2 + (1+1), 2+ (2)            -> D[2] = 2

    3 + {합이 1이되는 조합} = 3 + (1)                      -> D[1] = 1

  - 해서 답은 7

따라서, D[i]에 대해서도 똑깥이 생각해 보면,

D[i] = D[i-1] + D[i-2] + D[i-3], 4 <= i <= N


소스

#include <stdio.h>
int T,N,D[11];
int main() {
    int i, j;
    scanf("%d", &T);
    D[1] = 1; D[2] = 2; D[3] = 4;  
    while(T--){
        scanf("%d", &N);       
        for (i = 4; i <= N; ++i) D[i] = D[i - 1] + D[i - 2] + D[i - 3];
        printf("%d\n", D[N]);
    }
    return 0;
}



8. 동전 바꿔주기 (백준 2624)

명보네 동네 가게의 현금 출납기에는 k 가지 동전이 각각 n1, n2, … , nk개 씩 들어있다. 가게 주인은 명보에게 T원의 지폐를 동전으로 바꿔 주려고 한다. 이때, 동전 교환 방법은 여러 가지가 있을 수 있다. 예를 들어, 10원 짜리, 5원 짜리, 1원 짜리 동전이 각각 2개, 3개, 5개씩 있을 때, 20원 짜리 지폐를 다음과 같은 4가지 방법으로 교환할 수 있다.


20 = 10×2 

20 = 10×1 + 5×2 

20 = 10×1 + 5×1 + 1×5 

20 = 5×3 + 1×5

입력으로 지폐의 금액 T, 동전의 가지 수 k, 각 동전 하나의 금액 pi와 개수 ni가 주어질 때 (i=1, 2,…, k) 지폐를 동전으로 교환하는 방법의 가지 수를 계산하는 프로그램을 작성하시오. 방법 의 수는 231을 초과하지 않는 것으로 가정한다.


입력

첫째 줄에는 지폐의 금액 T(0<T≤10,000), 둘째 줄에는 동전의 가지 수 k(0<k≤100), 셋째 줄부터 마지막 줄까지는 각 줄에 동전의 금액 pi(0<pi≤T)와 개수 ni(0<ni≤1,000)가 주어진다. pi와 ni사이에는 빈칸이 하나씩 있다.


출력

첫째 줄에 동전 교환 방법의 가지 수를 출력한다. 방법이 없을 때는 0을 출력한다.


예제 입력  

20

3

5 3

10 2

1 5


예제 출력  

4


풀이

1. 2차원배열이용 

- D(i,j): 1~i개까지 동전을 이용해서, j금액을 만들 수 있는 최대 가짓수

- D(i,t*p(i)) = 1, 1<=t*p(i)<=T  ;i번째 동전만을 가지고 만들 수 있는 가짓수를 계산해 놓고,

  D(i,j) = Sum(D(i-1,j-t*p(i)), 2<=i<=N, 0<=t<=n(i) && 0<=t<=j/p(i)

- 즉, i-1번째 동전까지를 사용했을 때의 D값이 이미 계산되어 있다고 가정하면, i번째 동전에 대해서는, i번째 동전을 사용하던지 말던지의 경우가 생김. 사용하지 않는다면 D(i-1,j)와 같게 되는 것이고, 사용한다면 1개일지 2개일 지 등 사용갯수에 따라 경우수 늘어나게 될 것임

따라서, 이를 수식으로 표현한것이  Sum(D(i-1,j-t*p(i))


2. 1차원배열 이용

-1차원배열을 이용해서 풀수도 있다. 소스 참조


소스-2차원배쳘

#include <stdio.h>
 
int T, k;
int p[101], n[101], D[101][10001];
int main() {
    int i, j, t;
    int t1, t2;
    scanf("%d%d", &T, &k);
    for (i = 1; i <= k; ++i) scanf("%d%d", &p[i], &n[i]);
     
    for (i = 1; i <= k; ++i) for (t = 1; (t <= n[i]) && (t*p[i]<=T); ++t) D[i][t*p[i]] = 1;   
    for (i = 2; i <= k; ++i)  for (j = 1; j <= T; ++j)
        for (t = 0; (t*p[i] <= j) && (t <= n[i]); ++t)               
            D[i][j] += D[i-1][j-t*p[i]];               
     
    printf("%d\n",D[k][T]);
    return 0;
}

소스-1차원

#include <stdio.h>
int T,k,D[10001]={1,};
int main() {
    int i,j,t,s,p,n;   
    scanf("%d%d",&T,&k);
    for(i=1;i<=k;++i){
        scanf("%d%d",&p,&n);
        for (j=T;j>=p;--j){
            s=0;
            for(t=1;t<=n && t*p<=j;++t) s+=D[j-t*p];
            D[j]+=s;
        }
    }  
    printf("%d\n",D[T]);
    return 0;
}


9. 내리막 길 (백준 1520)

여행을 떠난 세준이는 지도를 하나 구하였다. 이 지도는 아래 그림과 같이 직사각형 모양이며 여러 칸으로 나뉘어져 있다. 한 칸은 한 지점을 나타내는데 각 칸에는 그 지점의 높이가 쓰여 있으며, 각 지점 사이의 이동은 지도에서 상하좌우 이웃한 곳끼리만 가능하다.




현재 제일 왼쪽 위 칸이 나타내는 지점에 있는 세준이는 제일 오른쪽 아래 칸이 나타내는 지점으로 가려고 한다. 그런데 가능한 힘을 적게 들이고 싶어 항상 높이가 더 낮은 지점으로만 이동하여 목표 지점까지 가고자 한다. 위와 같은 지도에서는 다음과 같은 세 가지 경로가 가능하다.



지도가 주어질 때 이와 같이 제일 왼쪽 위 지점에서 출발하여 제일 오른쪽 아래 지점까지 항상 내리막길로만 이동하는 경로의 개수를 구하는 프로그램을 작성하시오.


풀이

- Bottom up으로 풀지 못할 듯하다. 끝에서 재귀호출하며 메모이제이션하는게 속편함

- 상하좌우에서 접근 가능한데, 단 큰 수에서 작은 수로만 이동할 수 있다는 것이 제약조건이기에, 점화식을 다음과 같이 세울 수 있음

D(y,x): y행 x열까지의 최대 경우의 수. D(M,N)=?

D(y,x) = Sum( D(y+dy[k], x+dx[k])), when T(y+dy[k],x+dx[k]) > T(y,x),  1<=y<=M, 1<=x<=N, 0<=k<=3

dy={{-1,1,0,0}}, dx={0,0,-1,1}

- 위와같이 상하좌우에서 접근가능하도록 점화식을 세울 때 두려움은, 갔던 곳의 값이 다시 되먹임되어 합산되지 않을까 하는 두려움. 근데, 방향성이 수가 큰 곳에서 작은 곳이기에, A->B로 갈 수 있었다면 A<-B로는 못가기에 걱정하지 않아도 됨.


소스

#include <stdio.h>
int M,N;
int dy[4]={-1,1,0,0},dx[4]={0,0,-1,1},t[502][502],D[502][502];
int dp(int y,int x){
    int i,y2,x2;
    if(y==1 && x==1) return (D[y][x]=1);
    if(D[y][x]!=0) return D[y][x];
    for(i=0;i<4;++i){
        y2=y+dy[i]; x2=x+dx[i];
        if(t[y2][x2]>t[y][x]) D[y][x]+=dp(y2, x2);
    }
    return D[y][x];
}
int main() {
    int x,y;   
    scanf("%d%d", &M, &N);
    for(y=1;y<=M;++y) for(x=1;x<=N;++x) scanf("%d",&t[y][x]);
     
    printf("%d\n",dp(M,N));
    return 0;
}

-끝- 

반응형

'알고리즘 > 알고리즘 노트' 카테고리의 다른 글

[자료구조]-[리스트]-문제  (0) 2016.08.05
[자료구조]-[스택]-문제  (0) 2016.08.04
[다이나믹]-문제2  (0) 2016.07.30
[다이나믹]-문제1  (0) 2016.07.30
[문자열]  (0) 2016.07.28