알고리즘/알고리즘 노트

[다이나믹]-문제2

산을좋아한라쯔 2016. 7. 30. 07:00
반응형

1. 막대기

2. 합분해

3. 폐지줍기

4. 동전2

5. 돌다리 건너기

6. 바둑돌 잇기

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

1. 막대기

n가지 종류의 막대기가 있다. 막대기의 길이는 모두 다르다. 이 막대기들을 적당히 사용해서, 총 길이가 K가 되도록 하고 싶다. 그 경우의 수를 구하시오. (각각의 막대기는 여러번 사용할 수 있다.)


입력

첫째 줄에 n,k 가 주어진다. (1 <= n <= 100, 1 <= k <= 10,000) 다음 n개의 줄에는 각각의 막대기의 길이가 주어진다.


출력

첫째 줄에 경우의 수를 출력한다. 1000000으로 나눈 나머지를 출력하시오.



풀이

- n개가 아닌, n종류라는 데 주의. 즉, 1종류를 여러번 사용 가능

- D[i][j]: i개까지의 막대기를 이용해서, j 길이 만큼을 만들 수 있는 경우의 수

- D[0][0]=1  ==> 0개의 막대기를 이용해서 길이 0만큼 만들 수 있는 경우의 수

  D[i][j] = Sum(D[i-1][j - k x s[i]), 0 <= k <= j/s[i]

  ==> i개까지 막대기를 사용해서 j길이만큼을 만들 수 있는 경우의 수는, 

  한 개 덜 사용하면서(i-1개 사용하면서), i번째 막대기를 0번사용~j/s[i]번까지 사용할 때의 경우의 수를 모두 합한 것.

  즉, i번째 막대기를 안 사용할 수도 있고, 1개 사용할 수도 2개 사용할 수도 있는 것. 

  최대 몇개까지 사용할 수 있는 가는, j - k x s[i] > 0 이므로,  k <= j/s[i] 일 때까지.


소스

#include <stdio.h>

 

int N, K;

int s[101];

int D[101][10001];

int MOD = 1000000;

int solve() {

         D[0][0] = 1;

 

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

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

                           for (int k = (j / s[i]); k >= 0; --k) {

                                   D[i][j] += D[i - 1][j - k*s[i]];

                                   if (D[i][j] >= MOD) {

                                            D[i][j] -= MOD;

                                   }

                           }

                  }

         }

 

         return D[N][K];

}

 

int main()

{

         //freopen("stick.txt", "r", stdin);

         setbuf(stdout, NULL);

 

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

 

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

                  scanf("%d", &s[i]);

         }

 

         int ans = solve();

         printf("%d", ans);

 

         return 0;

}



2. 합분해

0부터 N까지의 정수 K개를 더해서 그 합이 N이 되는 경우의 수를 구하는 프로그램을 작성하시오.

덧셈의 순서가 바뀐 경우는 다른 경우로 센다(1+2와 2+1은 서로 다른 경우). 또한 한 개의 수를 여러 번 쓸 수도 있다.


입력

첫째 줄에 두 정수 N(1≤N≤200), K(1≤K≤200)가 주어진다.


출력

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


풀이

- D[K][N]을 구하는 것. 숫자를 K개 더해서 그 합이 N이 되는 경우의 수

- D[i][j]: 숫자를 i개 더해서, 그 합이 j가 되는 경우의 수

- D[i][j] = D[i-1][j] + D[i-1][j-1] + ... +D[i-1][0]  

           = Sum(D[i-1][k], 1<=i<=K, 0<=j<=N, 0<=k<=j    ;j가 0부터 시작함에 유의. 합이 0이되는 경우 있음

   ==> i개 더해서 합이 j가 되는 경우의 수는, i-1개 사용해서 j가 된 후 0을 더하거나(i번째 수로 0을 선택), i-1개 사용해서 합이 j-1인 상태에서 i번째로 1을 사용하거나,  j-2에서 2를 사용하는 등의 조합이 가능(수는 0~j까지 존재하니깐)

- D[0][0]=1 ==> 숫자 0개를 더해서 그 합이 0이되는 경우의 수는 1

소스

#include <stdio.h>

 

int N, K;

 

int D[203][203];

 

int main() {

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

 

         D[0][0] = 1;

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

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

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

                                   D[i][j] += D[i - 1][k];

                                   D[i][j] %= 1000000000;

                           }

                  }

         }

 

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

 

         return 0;

}




3. 폐지줍기

N * N 격자로 이루어진 도시가 있다. 이 도시 군데군데에는 폐지가 버려져 있다.


범수는 가장 왼쪽 위 격자 (1,1)에서 출발하여 가장 오른쪽 아래 격자 (N,N)까지 이동하며 폐지를 줍는데, 최단 경로를 따라가야만 한다. 즉, 인접한 오른쪽 칸 또는 아래쪽 칸으로만 이동할 수 있다. 이 때, 범수가 수집할 수 있는 폐지의 최대값을 출력하시오.


출발점과 도착점에 위치한 폐지 또한 주울 수 있다.


시간제한: 1초


입력

첫 줄에는 도시의 크기 N(2 ≤ N ≤ 200)이 주어진다. 그 다음 N개의 줄에 도시의 정보가 주어진다. 도시의 정보 중 i번째 줄의 j번째 숫자는 격자 (i, j)에 버려진 폐지의 양 A_ij을 나타낸다. (0 ≤ A_ij ≤ 1000)


출력

범수가 주울 수 있는 최대 폐지 양을 출력한다.


풀이

- D[i][j] : (i,j)칸에서의 폐지 최대값

  D[1][1] = a[1][1]

  D[i][j] = max(D[i][j-1], D[i-1][j]) + a[i][j]

  ==> a[i][j] += max(a[i][j-1], a[i-1][j] 로도 가능


소스

#include <stdio.h>

#include <algorithm>

 

using namespace std;

 

int n, p[210][210];

 

int main(){

         scanf("%d", &n);

         for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++)scanf("%d", p[i] + j);

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

                  for (int j = 1; j <= n; j++){

                           p[i][j] += max(p[i - 1][j], p[i][j - 1]);

                  }

         }

         printf("%d", p[n][n]);

         return 0;

}


4. 동전2 - 백준 2294번

n가지 종류의 동전이 있다. 각각의 동전이 나타내는 가치는 다르다. 이 동전들을 적당히 사용해서, 그 가치의 합이 k원이 되도록 하고 싶다. 그러면서 동전의 개수가 최소가 되도록 하려고 한다. (각각의 동전은 몇개라도 사용할 수 있다.)


입력

첫째줄에 n, k가 주어진다. (1 ≤ n ≤ 100, 1 ≤ k ≤ 100,000) 다음 n개의 줄에는 각각의 동전의 가치가 주어진다.


출력

첫째줄에 사용한 동전의 최소 개수를 출력한다. 불가능한 경우에는 -1을 출력한다.


예제입력

3 15

1

5

12


예제 출력

3


핵심

- D[i]: i 금액을 만들 수 있는 최소 동전 갯수 -> D[k]=?

- 먼저, 동전 한 종류로 만들 수 있는 D[i]값들을 구한다. 

  예를 들어 2원, 3원짜리 동전이 있다면,

  D[2*1] = 1, D[2*2]=2, D[2*3]=3,..

  D[3*1] = 1, D[3*2]=2, D[3*3]=3

  ==> 값을 갱신할 때, 기존값과 비교해서 작은 경우만 갱신

- 동전 한 종류로 만들 수 있는 최소D[i]들을 구했으니, 이제 동전 여러개의 조합을 이용해서의 D[i]값을 구할 수 있음

  i는 2~k까지, j는 1~(i/2)까지 변화시키면서. (j=0인경우, 검사 할 필요 없음)

  D[i] =  (D[i]==-1) ? D[j]+D[i-j] : min(D[i],D[j]+D[i-j], 2<=i<=k, 1<=j<=(i/2)


소스

#include <stdio.h>

#include <limits.h>

#include <algorithm>

 

#define MAX 103

using namespace std;

 

int n,k;

int p[MAX];

int D[MAX][100003];

/*

p[i]: i번째 동전 가치

D[i][j]: i번째 동전까지 사용해서, 금액j 만들 있는, 최소동전갯수. 1<=i<=n, 0<=j<=k

D[n][k]=?

D[0][0]=0, D[1][0]=0, D[1][p[i]]=1

 

D[i][j] = min(D[i-1][j] + 0 , D[i-1][j-p[i]] + 1,...)

= min(D[i-1][j-t*p[i]] + t), 0<=t<=(j/p[i])

 

*/

int main() {

         scanf("%d %d", &n,&k);

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

        

        

         for (int i = 0; i <= n; ++i)  for (int j = 0; j <= k; ++j)             D[i][j] = -1;

         D[0][0] = 0;

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

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

                           D[i][j] = INT_MAX;

                           for (int t = 0; t <= (j / p[i]); ++t) {

                                   int idx = j-t*p[i];

                                   if (D[i - 1][idx] != -1) D[i][j] = min(D[i][j], D[i-1][idx]+t);

                           }

                  }                

         }

         if (D[n][k] == INT_MAX) D[n][k] = -1;

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

         return 0;

}


5. 돌다리 건너기

절대반지를 얻기 위하여 반지원정대가 출발한다. 원정대가 지나가야할 다리는 두 개의 인접한 돌다리로 구성되어 있다. 하나는 <악마의 돌다리>이로 다른 하나는 <천사의 돌다리>이다.


아래 그림 1은 길이가 6인 다리의 한 가지 모습을 보여준다. 그림에서 위의 가로줄은 <악마의 돌다리>를 표시하는 것이고, 아래의 가로줄은 <천사의 돌다리>를 표시한다. 두 돌다리의 길이는 항상 동일하며, 각 칸의 문자는 해당 돌에 새겨진 문자를 나타낸다. 두 다리에 새겨진 각 문자는 {R, I, N, G, S} 중 하나이다.


출발 R I N G S R 도착

 G  R  G  G  N  S

 


반지원정대가 소유하고 있는 마법의 두루마리에 <악마의 돌다리>와 <천사의 돌다리>를 건너갈 때 반드시 순서대로 밟고 지나가야할 문자들이 적혀있다. 이 순서대로 지나가지 않으면 돌다리는 무너져 반지원정대는 화산 속으로 떨어지게 된다.


다리를 건널 때 다음의 제한 조건을 모두 만족하면서 건어야 한다.


1) 왼쪽(출발지역)에서 오른쪽(도착지역)으로 다리를 지나가야 하며, 반드시 마법의 두루마리에 적힌 문자열의 순서대로 모두 밟고 지나가야 한다.


2) 반드시 <악마의 돌다리>와 <천사의 돌다리>를 번갈아가면서 돌을 밟아야 한다. 단, 출발은 어떤 돌다리에서 시작해도 된다.


3) 반드시 한 칸 이상 오른쪽으로 전진해야하며, 건너뛰는 칸의 수에는 상관이 없다. 만일 돌다리의 모양이 그림 1고 같고 두루마리의 문자열이 "RGS"라면 돌다리를 건너갈 수 있는 경우는 다음의 3가지 뿐이다. (아래 그림에서 빨간색 문자는 밟고 지나가는 돌다리를 나타낸다.)


출발 R I N G S R 도착

 G  R  G  G  N  S

출발 R I N G S R 도착

 G  R  G  G  N  S

출발 R I N G S R 도착

 G  R  G     G  N  S

 


아래의 세 방법은 실패한 방법이다.


출발 R I N G S R 도착

 G  R  G     G    N  S

출발 R I N G S R 도착

 G  R  G  G  N  S

출발 R I N G S R 도착

 G  R  G  G  N  S

왜냐하면 첫 번째는 문자열 "RGS"를 모두 밟고 지나가야 하는 조건 1)을 만족하지 않으면, 두 번째는 번갈아가면서 돌을 밟아야 하는 조건 2)를, 세 번째는 앞으로 전진을 하여야하는 조건 3)을 만족하지 않기 때문이다.


마법의 두루마리에 적힌 문자열과 두 다리의 돌에 새겨진 문자열이 주어졌을 때, 돌다리를 통과할 수 있는 모든 가능한 방법의 수를 계산하는 프로그램을 작성하시오. 예를 들어, 그림 1의 경우는 통과하는 방법이 3가지가 있으므로 3을 출력해야 한다.


입력

첫째 줄에는 마법의 두루마리에 적힌 문자열(R, I, N, G, S 로만 구성된)이 주어진다. 이 문자열의 길이는 최소 1, 최대 20 이다. 그 다음 줄에는 각각 <악마의 돌다리>와 <천사의 돌다리>를 나타내는 같은 길이의 문자열이 주어진다. 그 길이는 1 이상, 100 이하이다.


출력

마법의 두루마리에 적힌 문자열의 순서대로 다리를 건너갈 수 있는 방법의 수를 출력한다. 그러한 방법이 없으면 0을 출력한다.


모든 테스트 데이터에 대한 출력결과는 231-1 이하이다.


예제 입력  

RGS

RINGSR

GRGGNS


예제 출력  

3


풀이

Tn = 목표 문자열

Am : 천사 돌다리

Bm : 악마 돌다리

D[i][j][0] : 천사돌다리로 끝나면서, i번째 목표문자열까지j번째 돌다리문자를 이용해서 만들 수 있는 최대 경우 수

D[i][j][1] : 악마돌다리로 끝나면서, ...

 

if(T[i] == A[j]) //목표문자열 i번째와 천사돌다리 j번째가 같다면

D[i][j][0] = Sum(D[i-1][k][1]), 0<= k <= j-1  // (j-1)까지의 악마돌다리에서의 경우의 수 합

if(T[i] == B[j]) //목표문자열 i번째와 악마돌다리 j번째가 같다면

D[i][j][1] = Sum(D[i-1][k][0]), 0<= k <= j-1  // (j-1)까지의 천사돌다리에서의 경우의 수 합

 

D[0][0][0] = D[0][0][1] = 1

  //0번째 목표문자열, 0번째 돌다리문자 이용 경우의 수로, 1의 의미가 좀 약하지만,

 //이렇게 해야 위의 점화식의 Sum값이 제대로 동작

 

ans = Sum(D[n][k][0] + D[n][k][1]), 1<= k <=m  //모두 더해줌에 유의. 달랑 D[n][m][0] + D[n][m][1] 이 아님

 //돌다리의 끝까지인 m까지 사용안하고도, 목표문자열을 만족시킬 수 있기 때문


소스

#include <stdio.h>
#include <string.h>
char T[23],A[103],B[103];
int D[23][103][2];
int main() {
    int i, j, k;
    int n,m;
    scanf("%s", &T[1]);
    scanf("%s %s", &A[1],&B[1]);
    n = strlen(&T[1]);
    m = strlen(&A[1]);
 
    int sum = 0;
    D[0][0][0] = D[0][0][1] = 1;
    for (i = 1; i <= n; ++i) {
        for (j = 1; j <= m; ++j) {
            if (T[i] == A[j]) for (k = 0; k <= (j - 1); ++k) D[i][j][0] += D[i-1][k][1];
            if (T[i] == B[j]) for (k = 0; k <= (j - 1); ++k) D[i][j][1] += D[i-1][k][0];        
        }
    }
    int ans=0;
    for (j = 1; j <= m; ++j) ans += (D[n][j][0] + D[n][j][1]);
    printf("%d\n", ans);
    return 0;
}

6. 바둑돌 잇기



-끝-


반응형

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

[자료구조]-[스택]-문제  (0) 2016.08.04
[다이나믹]-문제3  (0) 2016.07.30
[다이나믹]-문제1  (0) 2016.07.30
[문자열]  (0) 2016.07.28
[자료구조]  (0) 2016.07.27