9466. 텀 프로젝트





1. 접근


결국엔 사이클을 찾음과 동시에 사이클에 원소가 몇개나 있는지 알 수 있어야 한다.


심플함이 먹히니까 학생들을 노드로, 희망사항을 간선으로 생각하여 그래프를 구성하자.


이제 사이클을 찾는 작업은 심플한 DFS로 가능할 거이다.




2. 풀이


결국엔 모든 노드에 대해 DFS를 일단 실행시켜보긴 할 것이다.


함수는 시작점과, 현재 방문지점, 지금까지 몇개나 거쳐왔는지의 정보를 알고 있어야 한다.


이유는 다음과 같다.



노드의 상태는 방문한 적이 있거나, 방문한 기록이 없거나 양자택일이다.


방문한 적이 없다면 필요한 사항을 기록하고 간선을 따라 이동하면 될것이다.



방문한 적이 있다면 이유는 두가지중 하나다.


현재의 노드가 속한 사이클이 돌고 돌아서 다시 자기 자신에게 돌아왔거나,


아니면 현재 노드가 속하지 않은 사이클이 기록되고 이미 끝나버렸는데,

구질구질하게 현재의 노드는 그 사이클에 속하고 싶어하기 때문이다.



이 두가지를 구분하기 위해 시작점을 알아야 한다.


노드에 시작점을 기록했다면(사이클의 시작인 셈) 같은 사이클인지, 아닌지 구분할 수 있다.


같은 사이클이라면 현재의 깊이와 과거 기록되었던 깊이의 차이를 구하면, 사이클의 노드 수를 알 수 있다.



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
#include <stdio.h>
#include <string.h>
using namespace std;
 
int t, n, ans;
int arr[100001], visit[100001], cycle[100001];
 
int dfs(int start, int cur, int dep) {
    visit[cur] = dep;
    cycle[cur] = start;
 
    int next = arr[cur];
 
    if (visit[next] == 0) {
        dfs(start, next, dep + 1);
    }
    else if (visit[next] != 0 && start == cycle[next])
        return dep - visit[next] + 1;
    else if (visit[next] != 0 && start != cycle[next])
        return 0;
}
 
int main() {
    scanf("%d"&t);
    while (t--) {
        scanf("%d"&n);
 
        memset(visit, 0sizeof(visit));
        ans = 0;
 
        for (int i = 1; i <= n; ++i)
            scanf("%d"&arr[i]);
 
        for (int i = 1; i <= n; ++i) {
            if (visit[i] != 0)
                continue;
            if (visit[arr[i]] == 0)
                ans += dfs(i, i, 1);
        }
        printf("%d\n", n - ans);
    }
    return 0;
}
cs


'알고리즘 > 브루트 포스' 카테고리의 다른 글

백준) 2178 미로 탐색  (3) 2017.10.09
백준) 7569 토마토  (3) 2017.09.28
백준) 7576 토마토  (0) 2017.09.28
백준) 1194 달이 차오른다, 가자.  (0) 2017.07.07
백준) 2667 단지번호붙이기  (0) 2017.07.07

9742. 순열


문제

집합의 순열이란 집합의 서로 다른 원소를 모두 사용해 만들 수 있는 순서이다. 예를 들어, {2,3,5}의 순열은 다음과 같다.

  1. 2 3 5
  2. 2 5 3
  3. 3 2 5
  4. 3 5 2
  5. 5 2 3
  6. 5 3 2

각각의 순열은 숫자로 나타낼 수 있다. 위의 순열은 사전순으로 쓰여져 있으며, 등장하는 순서를 이용해 나타낸다. 즉, 3 5 2는 위치 4에 있고, 5 3 2는 마지막 위치인 6에 있다.

서로 다른 숫자와 문자로 이루어진 집합과 위치가 주어졌을 때, 그 집합의 순열 중 주어진 위치의 순열을 구하는 프로그램을 작성하시오.

입력

입력은 여러 개의 테스트 케이스로 이루어져 있다. 각 테스트 케이스는 한 줄로 이루어져 있다. 첫 번째 문자열은 서로 다른 숫자와 알파벳으로 이루어져 있으며, 길이는 최대 10이다. 또한, 사전순 순서대로 주어진다. 문자열 다음에는 찾아야 하는 위치가 주어진다.

출력

각각의 테스트 케이스마다, 입력으로 주어진 위치에 해당하는 순열을 공백없이 출력한다. 만약, 해당하는 순열이 없는 경우에는 "No permutation"을 출력한다.

예제 입력 

235 4
bein 20
123456 700
mnpqr 130
tuvwxyz 4000

예제 출력 

235 4 = 352
bein 20 = nbie
123456 700 = 651342
mnpqr 130 = No permutation
tuvwxyz 4000 = ywuxvzt 



1. 접근


순열을 만드는 규칙이 정해져있고, 몇 번째 순열을 출력해야 하는지 주어진다.


규칙은, 우리가 보통 경우의 수를 찾는 방법과 동일하다.


1,2,3,4 라면, 첫 번째 자리에 가장 작은 1을 적는다. 두 번째 자리엔 2,3,4 가 올 수 있다. 그 중 가장 작은 2을 적는다. ...


첫 번째 자리의 경우를 모두 적었으면, 그 다음으로 작은 2를 적고 동일한 과정을 반복한다.



당연히 (글자길이!)를 넘어가는 k번 째 순열을 존재하지 않으므로, 전처리로 팩토리얼을 미리 계산해주면 없는 경우는 쉽게 알 수 있다.


다만 위와같이 해당 순열을 빠르게 찾으려면 어떻게 해야할까?




2-1). 풀이 1


놀랍게도 알고리즘 헤더에는 위와 같은 문제를 해결해주는 함수가 기존재한다.


바로 next_permutation(iterator first, iterator last) 인데, 이 함수는 [first, last) 범위 안의 원소들을 다음 "사전 순서"로 정렬해준다.


사전 순서란 우리의 규칙과 동일하게 적용된다.


영어사전의 단어들이 어떤 순서로 소개되는지 생각해보면 된다.



그러면 간단하게 함수호출로 문제를 풀 수 있을 것이다. k번 째 순열을 찾기 위해서 함수를 k번 호출하면 될 것이다.



2-2). 풀이 2


그러면 저 함수는 어떻게 구현되어 있는 걸까?


다음에서 정보를 얻었다.


https://stackoverflow.com/questions/11483060/stdnext-permutation-implementation-explanation



사전 순으로 1,4,3,2 의 다음 순열은 2,1,3,4 이다.


눈치 깔 수 있는 것은, 1의 오른쪽이 전부 내림차순이면 1의 자리수는 다음으로 큰 수로 바뀌고, 나머지는 오름차순으로 다시 정렬된다는 것이다.


마찬가지로, 1,3,4,2의 다음 순열은 1,4,2,3 이다.


오른쪽이 전부 오름차순이 아니게 되는 첫번째 원소는 3이었다. 그러면 3은 다음으로 큰 수로 바뀌고, 나머지는 오름차순으로 다시 정렬된다.



그러면 구현해야 할 것은 두 가지다. 


1. 오름차순이 아니게 되는 원소를 뒤에서 부터 찾은 다음, 그 원소보다 다음으로 큰 수를 찾아야하고


2. 나머지를 오름차순으로 정렬해야 한다.



1번은 쉽게 할 수 있다. 우리가 찾은 원소는 오른쪽이 오름차순이 아니게 되는 첫 원소이므로, 다음으로 큰 수는 뒤에서 부터 원소보다 처음으로 큰 수다.


2번은 그냥 sort를 쓸 수도 있겠지만, 다음과 같이 더 빠르게 수행할 수 있다.



먼저 1번에서 찾은 원소와 다음으로 큰 수를 서로 바꾼다. 1,3,4,2 였다면 3과 4를 바꿔서 1,4,3,2가 되었을 것이다.


우리가 찾은 원소는 오른쪽이 오름차순이 아니게 되는 첫 원소이므로, 나머지 두 자리수는 자명히 내림차순이다. 따라서 역순으로 뒤집어 주면 된다.


-> 1,4,2,3



1,4,5,3,2 의 다음 사전 순 순열은 1,5,2,3,4 이다. 1번을 수행하고 찾은 원소는 4다. 4보다 큰 수는 5임을 뒤에서 부터 찾을 수 있다.


4와 5를 바꾼다. 1,5,4,3,2 가 된다. 당연히 5의 오른쪽은 전부 내림차순이다. 오른쪽을 전부 뒤집어주자. -> 1,5,2,3,4 




2-3) 풀이 3


사실 수학적으로도 풀 수 있다.


1,2,3,4 로 4자리의 순열이라면, 총 4! = 24의 경우가 있는데, 이는 크게 첫 자리별로 4개의 집합으로 분류할 수 있다.


또 각각의 집합 안에서, 남은 세 자리 중에 첫 자리(실제론 두번째 자리)별로 3개의 집합으로 분류할 수 있다.



예를 들어, a,b,c,d 의 17번째 사전 순 순열을 알고 싶다면, 첫 자리별로 3! = 6 의 경우를 갖고 있으니까 16 / 6 = 2다.


(모듈러 연산을 위해 0, 1, 2, ... 순으로 순서를 매기자.)


이는 첫 자리가 arr[2] = c 임을 알려준다.



남은 세 자리에 대해 생각해보면 총 6가지 경우가 있으니까 16 % 6 = 4다. 이 말은 그 집합 안에서 다섯번째 순열이란 뜻이다.


4를 2!으로 나누면 2가 나온다. 이 말은 두 번째 자리가 남은 a, b, d 중 arr[2] = d 라는 뜻이다.



남은 두 자리에 대해 생각해보면 총 2 가지 경우가 있으니까 4 % 2 = 0이다. 이는 그 집합 안에서 첫번째 순열이란 뜻이다.


0을 1!으로 나누면 0이 나온다. 이는 세 번째 자리가 남은 a, b 중 arr[0] = a 라는 뜻이다.



이제 구현은 재귀적으로 할 수 있다.


자리 수를 채워나가면서 맨 앞자리를 가리키는 포인터를 전진시키면서 넘겨주면 된다. 



3-1) 코드 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
32
33
34
35
36
37
#include <iostream>
#include <string>
#include <stdio.h>
#include <algorithm>
using namespace std;
 
int fact[12= { 1, };
 
int main() {
 
    for (int i = 1; i <= 10; i++)
        fact[i] = fact[i - 1* i;
 
    string a;
    int n;
 
    while (cin >> a >> n) {
 
        cout << a << " " << n << " = ";
 
        if (n > fact[a.length()]) {
            cout << "No permutation" << endl;
            continue;
        }
 
        int cnt = 0;
        do {
            cnt++;
            if (cnt == n) {
                cout << a << endl;
                break;
            }
        } while (next_permutation(a.begin(), a.end()));
    }
 
    return 0;
}
cs



3-2) 코드 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
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
 
int n, k;
int fact[12];
int num[11];
char str[20];
 
bool next_per(int x, int y) {
    if (x == y)
        return false;
 
    int i = y;
 
    while (1) {
        int j = i;
        --i;
 
        if (num[i] < num[j]) {
            int k = y + 1;
            
            while (!(num[i] < num[--k]));
 
            int tmp = num[i];
            num[i] = num[k];
            num[k] = tmp;
 
            for (int t1 = j, t2 = y; t1 <= (j + y) / 2++t1, --t2) {
                int tmp = num[t1];
                num[t1] = num[t2];
                num[t2] = tmp;
            }
            return true;
        }
 
        else if (i == x) {
            for (int t1 = x, t2 = y; t1 <= (x + y) / 2++t1, --t2) {
                int tmp = num[t1];
                num[t1] = num[t2];
                num[t2] = tmp;
            }
            return false;
        }
    }
}
 
int main() {
    fact[0= 1;
    for (int i = 1; i <= 10; i++) {
        fact[i] = fact[i - 1* i;
    }
        
    while (~scanf("%s %d", str, &k)) {
        n = strlen(str);
        for (int i = 0; i < n; ++i)
            num[i] = i;
        int cnt = 0;
 
        printf("%s %d = ", str, k);
        if (k > fact[n] || k <= 0)
            puts("No permutation");
        else {
            while (++cnt != k)
                next_per(0, n - 1);
 
            for (int i = 0; i < n; ++i)
                printf("%c", str[num[i]]);
            puts("");
        }
    }
    return 0;
}
cs



3-3) 코드 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
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
 
int n, k;
int fact[12= { 1, };
char str[20];
 
void func(char *s, int x, int f) {
    if (f < 1return;
 
    int i = x / fact[f - 1];
    int last = s[i];
 
    while (i-- > 0)
        s[i + 1= s[i];
    
    s[0= last;
 
    func(s + 1, x % fact[f - 1], f - 1);
}
 
int main() {
    for (int i = 1; i <= 10; i++)
        fact[i] = fact[i - 1* i;
 
    while (scanf("%s %d", str, &k) != EOF) {
        n = strlen(str);
 
        printf("%s %d = ", str, k--);
 
        if (k >= fact[n] || k < 0)
            puts("No permutation");
 
        else {
            func(str, k, n);
            puts(str);
        }
    }
 
    return 0;
}
cs



4. 후기


처음엔 계속 배열을 두고 쓴 자리수를 체크체크하는 무지막지한 방법으로 124ms로 들어왔었다.


풀이 1 : 12 ms


풀이 2 : 20 ms


풀이 3 : 0 ms

10844. 쉬운 계단 수



문제

45656이란 수를 보자.

이 수는 인접한 모든 자리수의 차이가 1이 난다. 이런 수를 계단 수라고 한다.

세준이는 수의 길이가 N인 계단 수가 몇 개 있는지 궁금해졌다.

N이 주어질 때, 길이가 N인 계단 수가 총 몇 개 있는지 구하는 프로그램을 작성하시오. (0으로 시작하는 수는 없다.)

입력

첫째 줄에 N이 주어진다. N은 1보다 크거나 같고, 100보다 작거나 같은 자연수이다.

출력

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

예제 입력 1 

1

예제 출력 1 

9

예제 입력 2 

2

예제 출력 2 

17



1. 접근


인접한 모든 자리수의 차이가 1인 수를 계단 수라고 한다.


자리수가 주어질 때, 가능한 계단 수는 몇개나 되는가??



직관적인 다이나믹 프로그래밍이 가능하겠다.


작은 계단 수에서 큰 계단 수를 만들거나, 큰 계단 수에서 작은 계단 수를 만들 수도 있다.




2-1) 탑-다운 풀이


길이가 n인 계단 수는 어떤 수에서 비롯되었을까?


예를 들어 끝이 4라면, 이 계단 수는 끝이 3이거나 5인 계단 수로 만들 수 있다. (3 ->34 or 5 -> 54)



따라서


func(n,x) = 자리수가 n이고 끝이 x인 계단 수의 총 갯수 라고 정의 한다면 다음과 같다.


func(n, x) = func(n-1,x-1) + func(n-1,x+1)


끝의 예외 케이스를 정리해주면 쉽게 재귀적으로 구현 할 수 있다.



2-2) 바텀-업 풀이


길이가 n인 계단 수로 길이가 n+1인 계단 수를 몇개나 만들 수 있을까?


끝이 0이나 9가 아닌 이상 2개나 만들 수 있음은 자명하다.



그러면 이중 포문을 통해, 길이가 가장 작은 경우에서 끝 자리의 10가지 경우를 기록하고, 


이를 토대로 길이를 1늘려서 다시 확인하는 코드가 떠오를 것이다.


dp[d][x] = dp[d-1][x-1] + dp[d-1][x+1]


그게 맞당!




3-1) 코드 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
32
33
34
35
36
37
#include <stdio.h>
#include <string.h>
using namespace std;
 
#define MOD 1000000000
 
int n, ans;
int dp[101][10];
 
int func(int d, int x) {
    if (d == 1)
        return x == 0 ? 0 : 1;
 
    int& ref = dp[d][x];
    if (ref != -1)
        return ref;
 
    if (x == 0)
        return ref = func(d - 11) % MOD;
    if (x == 9)
        return ref = func(d - 18) % MOD;
    else
        return ref = (func(d - 1, x - 1+ func(d - 1, x + 1)) % MOD;
}
 
int main() {
    scanf("%d"&n);
 
    memset(dp, -1sizeof(dp));
 
    for (int i = 0; i <= 9++i)
        ans = (ans + func(n, i)) % MOD;
 
    printf("%d", ans);
 
    return 0;
}
cs



3-2) 코드 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
#include <stdio.h>
 
#define MOD 1000000000
 
int dp[101][10];
int n, ans = 0;
 
int main(void) {
    scanf("%d"&n);
 
    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 == 0)
                dp[i][0= dp[i - 1][1] % MOD;
            else if (j == 9)
                dp[i][9= dp[i - 1][8] % MOD;
            else
                dp[i][j] = (dp[i - 1][j - 1+ dp[i - 1][j + 1]) % MOD;
        }
    }
 
    for (int i = 0; i < 10; i++)
        ans = (ans + dp[n][i]) % MOD;
 
    printf("%d\n", ans);
    return 0;
}
cs


'알고리즘 > Dynamic Programming 동적계획법' 카테고리의 다른 글

백준) 1102 발전소  (0) 2017.09.05
백준) 1562 계단  (0) 2017.09.04
백준) 11049 행렬 곱셈 순서  (0) 2017.08.25
백준) 9520 NP-hard (PUTNIK)  (0) 2017.08.24
백준) 10835 카드게임  (2) 2017.08.24

11049. 행렬 곱셈 순서



문제

크기가 N×M인 행렬 A와 M×K인 B를 곱할 때 필요한 곱셈 연산의 수는 총 N×M×K번이다. 행렬 N개를 곱하는데 필요한 곱셈 연산의 수는 행렬을 곱하는 순서에 따라 달라지게 된다.

예를 들어, A의 크기가 5×3이고, B의 크기가 3×2, C의 크기가 2×6인 경우에 행렬의 곱 ABC를 구하는 경우를 생각해보자.

  • AB를 먼저 곱하고 C를 곱하는 경우 (AB)C에 필요한 곱셈 연산의 수는 5×3×2 + 5×2×6 = 30 + 60 = 90번이다.
  • BC를 먼저 곱하고 A를 곱하는 경우 A(BC)에 필요한 곱셈 연산의 수는 3×2×6 + 5×3×6 = 36 + 90 = 126번이다.

같은 곱셈이지만, 곱셈을 하는 순서에 따라서 곱셈 연산의 수가 달라진다.

행렬 N개의 크기가 주어졌을 때, 모든 행렬을 곱하는데 필요한 곱셈 연산 횟수의 최소값을 구하는 프로그램을 작성하시오.

입력으로 주어진 행렬의 순서를 바꾸면 안된다.

입력

첫째 줄에 행렬의 개수 N(1 ≤ N ≤ 500)이 주어진다.

둘째 줄부터 N개 줄에는 행렬의 크기 r과 c가 주어진다. (1 ≤ r, c ≤ 500)

항상 순서대로 곱셈을 할 수 있는 크기만 입력으로 주어진다.

출력

첫째 줄에 입력으로 주어진 행렬을 곱하는데 필요한 곱셈 연산의 최소값을 출력한다. 정답은 231-1 보다 작거나 같은 자연수이다.

예제 입력 

3
5 3
3 2
2 6

예제 출력 

90



1. 접근


행렬 곱셈 연산의 수를 계산하는 방법이 따로 존재할 때, 임의의 행렬들이 주어질 때(순서는 고정), 어떤 행렬들 부터 곱해나가야 연산을 가장 적게하는지 묻는 문제다.


행렬들이 ABCD 로 주어진다면 다음의 곱셈 순서들이 가능하다


1) { { { A * B } * C } * D }


2) { { A * B } * { C * D } }


3) { { A * { B * C } } * D }

 

4) { A * { B * { C * D } } }


5) { A * { { B * C } * D } }



역시나 위의 순서들을 빠르게 만들어 낼 수 있는 패턴을 찾아내는게 포인트다. 


그러면 패턴을 구현해내고, 다이나믹 프로그래밍으로 빠르게 연산 수를 계산할 수 있겠다.




2. 풀이


패턴 : 묶음은 그보다 작은 두 묶음으로 재귀적으로 나눠진다.


2)의 경우 가장 큰 묶음은 { A * B } 와 { C * D } 로 나눠진다. 나머지 경우들은 2번씩 나눠진다.


그러면 DP 배열을 다음과 같이 정의해보자.


DP[X][Y] = 인덱스 x인 행렬부터 y까지 주어졌을 때, 우리가 원하는 정답을 보관한다.



그러면 다음과 같은 재귀함수로 배열을 갱신해 나갈 수 있다.


func(int x, int y) = DP[X][Y]를 계산하는 함수


재귀함수가 탈출하기위한 기저사례는 무엇일까?


간단히 두행렬을 곱하는 경우는 쉽게 연산 수를 계산할 수 있다.


하지만 구현을 더 간단히 하기 위해 x==y 인 경우로 탈출시키자. -> 연산하지 않는다 = return 0



이제 두 묶음으로 나눌 수 있는 경우들을 구현해야 한다.


나누는 기준은 x~y까지 가능하다. 


for (int k = x; k < y; ++k)

mm = min(mm, func(x, k) + func(k + 1, y) + m[x].r * m[k].c * m[y].c);


// m[k].c = m[k+1].r


나눠주고 계산해주는 함수들을 호출한 다음, 최소값을 기록하면 된다.




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
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <algorithm>
using namespace std;
 
struct mat {
    int r, c;
};
mat m[501];
 
int dp[501][501];
int n, i;
 
int func(int x, int y) {
    if (x == y)
        return 0;
 
    int& ref = dp[x][y];
    if (ref != -1)
        return ref;
 
    int mm = INT_MAX;
 
    for (int k = x; k < y; ++k)
        mm = min(mm, func(x, k) + func(k + 1, y) + m[x].r * m[k].c * m[y].c);
 
    return ref = mm;
}
 
int main() {
    scanf("%d"&n);
    for (i = 0; i < n; ++i)
        scanf("%d %d"&m[i].r, &m[i].c);
 
    memset(dp, -1sizeof(dp));
 
    printf("%d", func(0, n - 1));
    return 0;
}
cs



4. 후기


하나씩 곱해야 하는 줄 알고 삽질좀 했다.

'알고리즘 > Dynamic Programming 동적계획법' 카테고리의 다른 글

백준) 1562 계단  (0) 2017.09.04
백준) 10844 쉬운 계단 수  (3) 2017.08.31
백준) 9520 NP-hard (PUTNIK)  (0) 2017.08.24
백준) 10835 카드게임  (2) 2017.08.24
백준) 3032 승리(IVANA)  (0) 2017.08.23

9520. NP-hard



문제

TSP는 매우 유명한 문제이다. 이 문제는 NP-hard 문제이기 때문에 효율적인 해법이 존재하지 않는다. 이번에는 약간 변형된 TSP 문제를 풀어보자.

TSP문제는 도시 N개를 모두 한 번씩 방문하는 문제이다. 도시는 1번부터 N번까지 번호가 붙여져 있다. 각 도시 사이의 거리는 모두 알려져 있으며, 모든 도시를 방문하는데 드는 시간을 최소로 해야 한다.

번호가 K인 도시를 방문하려면, K보다 작은 번호를 가진 모든 도시를 K번을 방문하기 전에 모두 방문하거나, 방문한 후에 모두 방문해야 한다. 즉, K보다 번호가 작은 도시 중 하나를 K번 이전에 방문하고, 다른 하나를 K번 이후에 방문하면 안된다.

위의 조건을 만족하면서 모든 도시를 방문하는데 드는 시간의 최소값을 구하는 프로그램을 작성하시오. 첫 도시와 마지막 도시는 같지 않아도 되며, 어느 도시에서 시작하고 마쳐도 상관없다.

입력

첫째 줄에 도시의 수 N (2 ≤ N ≤ 1500)이 주어진다.

다음 N개 줄에는 [0, 1000] 구간에 포함되는 양의 정수가 주어진다. A번째 행의 B번째 숫자는 A에서 B로 가는데 필요한 시간이고, 이 수는 B번 행의 A번째 숫자와 같다. A와 B가 같은 경우에는 0이 주어지며, 이외의 경우에는 항상 양의 정수이다.

출력

첫째 줄에 문제의 조건을 만족하면서 모든 도시를 방문하는데 드는 시간의 최소값을 출력한다.

예제 입력 

3
0 5 2
5 0 4
2 4 0

예제 출력 

7



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



1. 접근


모든 도시들은 서로 연결되어 있고, 일련의 규칙으로 모든 도시를 방문해야 한다. 모든 경우 중에 최소 비용을 구해야 한다.


이 일련의 규칙이란


1) k도시를 방문하려면 k보다 작은 도시들을 전부 미리 방문했거나,

2) k를 방문한 이후 딴짓 하지 말고 k보다 작은 모든 도시들을 이후에 방문해야 한다.


따라서 도시가 1, 2 두 개라면,


[1->2] 혹은 [2->1] 이 가능하다.


도시가 3까지 있다면,


[1->2->3] 혹은 [3->1->2] 혹은 [3->2->1] 혹은 [2->1->3] 이 가능하다


[1->3->2] 순서는 3을 방문하려고 했는데, 2번 규칙에 의하면 1도시를 먼저 방문해버렸기 때문에 옳지 않은 방법이다.


[2->3->1] 순서는 2번 규칙에 의하면 2도시를 방문했으면 딴짓 하지 말고 작은 도시를 방문해야 하지만

3도시로 가버렸으므로 옳지 않은 방법이다.



이와 같은 방문순서들을 n이 주어졌을 때 만드는 패턴이 있지 않을까?


만약 그렇다면 우리는 빠르게 만들어진 순서들을 시도해보는 다이나믹 프로그래밍을 할 수 있을 텐데!




2. 풀이


패턴은 다음과 같다


먼저 1을 시퀀스에 추가한다.

그 다음 2를 시퀀스의 왼쪽이나 오른쪽에 추가한다.

그 다음 3을 시퀀스의 왼쪽이나 오른쪽에 추가한다.

...


위의 패턴으로 우리는 규칙을 만족하는 모든 방법을 만들어 낼 수 있다.


귀납적으로 증명해보자.


1) 1밖에 없는 시퀀스는 규칙을 만족한다.

2) n까지 위의 패턴으로 만든 시퀀스가 규칙을 만족한다고 가정해보자.

3) n+1 도시를 왼쪽이나 오른쪽에 추가해도 규칙을 만족한다면 우리의 패턴은 참이다.


2단계 까지 이뤄진 시퀀스의 가장 번호가 큰 도시는 당연히 n도시다.


이제 3단계에서 n+1 도시를 왼쪽에 추가한다고 하면, 우리는 규칙 2를 만족하는 방문순서를 만들어 낸것이다.

반대로 오른쪽에 추가한다고 하면, 이 시퀀스는 규칙 1을 지키는 방문순서가 될 것이다.



이제 패턴에 따라 모든 경우를 계산해 줄 함수를 정의해보자.


func(현재 시퀀스의 왼쪽 도시 번호, 현재 시퀀스의 오른쪽 도시 번호, 추가할 도시 번호)

= 현재 상황에서의 만들 수 있는 종래의 최소비용 = func(int x, int y, int num)


기저사례는 num이 n+1까지 확장될 경우다. 더 이상 추가할 도시가 없다.


이 도시 num을 왼쪽에 추가한다면, 시퀀스의 왼쪽은 num이 될 것이므로,


func(num, y, num+1) + dist[x][num] 


이 도시 num을 오른쪽에 추가한다면, 시퀀스의 오른쪽은 num이 될 것이므로,


func(x, num, num+1) + dist[y][num]



두 경우를 계산해보고, 둘 중 작은 값이 정답이다. 메모이제이션으로 O(n*n).



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
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
 
int dp[1501][1501];
int dist[1501][1501];
int n, i, j;
 
int func(int x, int y, int num) {
    if (num == n + 1)
        return 0;
 
    int& ref = dp[x][y];
    if (ref != -1)
        return ref;
 
    int left = func(num, y, num + 1+ dist[x][num];
    int right = func(x, num, num + 1+ dist[y][num];
 
    return ref = min(left, right);
}
 
int main() {
    scanf("%d"&n);
    for (i = 1; i <= n; ++i)
        for (j = 1; j <= n; ++j)
            scanf("%d"&dist[i][j]);
 
    memset(dp, -1sizeof(dp));
 
    printf("%d", func(1,1,1));
 
    return 0;
}
cs



4. 후기


패턴을 못찾았다면 난리 필 문제

10835. 카드게임


문제

지훈이는 최근에 혼자 하는 카드게임을 즐겨하고 있다. 게임에 사용하는 각 카드에는 양의 정수 하나가 적혀있고 같은 숫자가 적힌 카드는 여러 장 있을 수 있다. 게임방법은 우선 짝수개의 카드를 무작위로 섞은 뒤 같은 개수의 두 더미로 나누어 하나는 왼쪽에 다른 하나는 오른쪽에 둔다. 그리고 빈 통을 하나 준비한다. 

이제 각 더미의 제일 위에 있는 카드끼리 서로 비교하며 게임을 한다. 게임 규칙은 다음과 같다. 지금부터 왼쪽 더미의 제일 위 카드를 왼쪽 카드로, 오른쪽 더미의 제일 위 카드를 오른쪽 카드로 부르겠다.

  1. 언제든지 왼쪽 카드만 통에 버릴 수도 있고 왼쪽 카드와 오른쪽 카드를 둘 다 통에 버릴 수도 있다. 이때 얻는 점수는 없다.
  2. 오른쪽 카드에 적힌 수가 왼쪽 카드에 적힌 수보다 작은 경우에는 오른쪽 카드만 통에 버릴 수도 있다. 오른쪽 카드만 버리는 경우에는 오른쪽 카드에 적힌 수만큼 점수를 얻는다.
  3. (1)과 (2)의 규칙에 따라 게임을 진행하다가 어느 쪽 더미든 남은 카드가 없다면 게임이 끝나며 그때까지 얻은 점수의 합이 최종 점수가 된다. 

다음 예는 세 장 씩 두 더미의 카드를 가지고 게임을 시작하는 경우이다

카드 순서왼쪽 더미오른쪽 더미
132
224
351

이 경우, 우선 오른쪽 카드 2가 왼쪽 카드 3보다 작으므로 규칙 (1)에 따라 왼쪽 카드만 버리거나 왼쪽 카드와 오른쪽 카드를 모두 버리거나, 규칙 (2)에 따라 오른쪽 카드만 버릴 수 있다.

만약 오른쪽 카드만 버리는 것으로 선택하면, 2만큼 점수를 얻고 오른쪽 카드 2는 버린다.

이제 오른쪽 더미의 제일 위 카드는 4이고 이는 왼쪽 카드 3보다 크므로 규칙 (1)에 따라 왼쪽 카드만 버리거나 왼쪽 카드와 오른쪽 카드를 둘 다 버릴 수 있다.

만약 둘 다 버리는 것으로 선택하면, 이제 왼쪽 카드는 2가 되고 오른쪽 카드는 1이 된다.

이 경우 다시 규칙 (1)과 (2)에 따라 세 가지 중 한가지를 선택할 수 있고, 그 중 왼쪽 카드만 버리는 것으로 선택하면 이제 왼쪽 카드는 5가 되고 오른쪽 카드는 1이 된다.

이 경우에도 역시 규칙 (1)과 (2)에 따라 세 가지 중 한가지를 선택할 수 있고, 그 중 오른쪽 카드만 버리는 것으로 선택하면 1만큼 점수를 얻고 오른쪽 카드 1은 버린다.

이제 오른쪽 더미에는 남은 카드가 없으므로 규칙 (3)에 따라 게임이 끝나며 최종 점수는 2+1=3이 된다.

두 더미의 카드가 주어졌을 때, 게임을 통해 얻을 수 있는 최종 점수의 최대값을 출력하는 프로그램을 작성하시오.

위 예에서 최종 점수의 최대값은 7이다.

입력

첫 줄에는 한 더미의 카드의 개수를 나타내는 자연수 N(1 ≤ N ≤ 2,000)이 주어진다. 다음 줄에는 왼쪽 더미의 카드에 적힌 정수 A(1 ≤ A ≤ 2,000)가 카드 순서대로 N개 주어진다. 그 다음 줄에는 오른쪽 더미의 카드에 적힌 정수 B(1 ≤ B ≤ 2,000)가 카드 순서대로 N개 주어진다. 각 더미에는 같은 숫자를 가진 카드가 두 개 이상 있을 수 있다.

출력

얻을 수 있는 최종 점수의 최대값을 출력한다.

예제 입력 

3
3 2 5
2 4 1

예제 출력 

7



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



1. 접근


규칙에 따라 게임을 해야 할 때, 얻을 수 있는 최대의 점수를 구해야 한다.


어쩌면 처음의 카드 배열을 보고 최대 점수를 얻는 전략이 있을 수도 있다.

하지만 빡대가리로서는 짐작도 안가므로, 모든 경우를 계산해보기로 하자.


또한 다이나믹 프로그래밍으로 시간을 절약해 보기로 하자.



2. 풀이


자연스럽게 재귀적인 풀이가 떠오른다.


func(왼쪽 더미의 남은 카드 수, 오른쪽 더미의 남은 카드 수) = 이런 상황에서 얻을 수 있는 최대의 점수

= func(int x, int y)


라는 함수로 모든 계산을 맡겨 보기로 한다.


현재 상황에서 일단 내릴 수 있는 선택은 총 세가지다.


1) 왼쪽을 버린다 (+0점) -> func(x-1, y)

2) 왼쪽, 오른쪽 둘 다 버린다 (+0) func(x-1, y-1)

3) 왼쪽 카드 점수(a점) < 오른쪽 카드 점수(b점) 이라면 오른쪽을 버린다 (+b점) -> func(x,y-1) + b


따라서 함수내에 재귀적으로 세가지 함수를 호출해주면 된다.


기저사례는 양쪽 카드 더미 중 한쪽이라도 바닥나는 경우다. 



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
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
 
int dp[2001][2001];
int a[2001], b[2001];
int n, i;
 
int func(int x, int y) {
    if (x == 0 || y == 0)
        return 0;
 
    int& ref = dp[x][y];
    if (ref != -1)
        return ref;
 
    int c1 = func(x - 1, y);
    int c2 = func(x - 1, y - 1);
    int c3 = 0;
 
    if (a[x] > b[y])
        c3 = func(x, y - 1+ b[y];
 
    return ref = max(max(c1, c2), c3);
}
 
int main() {
    scanf("%d"&n);
    for (i = 0; i < n; ++i)
        scanf("%d"&a[n - i]);
    for (i = 0; i < n; ++i)
        scanf("%d"&b[n - i]);
 
    memset(dp, -1sizeof(dp));
 
    printf("%d", func(n, n));
 
    return 0;
}
cs



4. 후기


응^^ 중등부

3032. 승리


문제

상근이의 여동생 선영이는 정인이가 상근이의 마이크로프로세서를 훔치는 것을 보았다. 선영이는 상근이에게 이 사실을 말해야 하는지를 고민했다. 하지만, 선영이는 상근이보다 정인이를 더 좋아하기 때문에 말하지 않았다.

선영이는 정인이에게 데이트 신청을 하였고, 영화를 같이 보러간다면 이 사실을 그 누구에게도 말하지 않기로 약속했다.

알고보니 정인이는 여자에게 별 관심이 없었다. 그 이유는 정인이가 수학문제를 푸는데 방해 된다고 생각하기 때문이다.

따라서, 그는 선영이에게 게임을 신청했고, 이기면 영화를 보러 가기로 했다.

선영이는 게임에 자신이 있었기 때문에, 정인이의 제안에 동의했다.

정인이는 N개의 양수를 원 위에 차례대로 쓰고 선영이에게 게임의 규칙을 설명했따.

1. 첫 번째 플레이어가 아무 숫자나 하나 고른다.

2. 두 번째 플레이어가 첫 번째 플레이어가 고른 수에 인접한 두 수 중 하나를 고른다.

3. 그 다음 플레이어는 고를 수 있는 수가 없을 때까지 지금까지 고른 수에 인접한 수 중에서 아무 숫자나 고른다.

4. 홀수를 가장 많이 고른 사람이 승리한다.

정인이는 최선을 다해서 게임을 한다. 그는 항상 이기거나 비길 수 있는 전략을 선택한다.

정인이는 선영이가 게임을 얼마나 잘 하는지 모른다. 또, 정인이는 신사이기 때문에, 선영이에게 첫 번째 수를 양보했다.

선영이가 게임을 승리하기 위해서 고를 수 있는 첫 번째 수의 총 개수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 원 위에 있는 수의 개수 N이 주어진다. (1 ≤ N ≤ 100)

둘째 줄에는 N개의 정수가 공백으로 구분되어 주어진다. 모든 숫자는 1 이상 1000 이하이다. 두 수가 같은 경우는 없다.

출력

선영이가 게임을 승리하기 위해서 고를 수 있는 첫 번째 수의 개수를 첫째 줄에 출력한다.

예제 입력 

3
3 1 5

예제 출력 

3



1. 접근


두 게이머가 게임을 한다.


다만 두 명 모두 최선의 전략을 구사할 줄 알고(근데 우리는 모른다) 게임에 임할 때,

첫 순서인 플레이어가 이길 수 있는 경우의 수를 묻는 문제다.


역시나 짱구를 굴려봐도 최선의 전략이 뭔지 모르겠으니까, 모든 경우를 시도해보는(하지만 빠른) 다이나믹 프로그램을 써보자.


고려할 점은 n개의 수가 일렬로 있는게 아니라 원형 배열에 있다는 점이다.



2. 풀이


처음의 선택으로 원형배열은 일렬의 배열로 바뀐다. 선택의 양쪽 수가 배열의 양쪽 끝 수가 될 것이다.

 

그러면 카드게임 : http://js1jj2sk3.tistory.com/33 의 경우와 같아진다.

이기기 위한 전략이 뭔지는 모르겠지만, 나의 점수를 최대한으로 하려는 것이다. (상대의 점수를 최소한으로 하려는 것이다)


다만 문제가 요구하는 것은 첫 번째 선택으로 올바른 경우를 세야 하므로 지금까지의 turn을 고려하는 함수는 만들기 어렵다.

(내 머리로 못함)


func(int x, int y) = 남은 배열(입력받은 배열)이 x부터 y일때, 낼 수 있는 최대한의 점수차이.


위와 같이 정의한다면 현재 순서가 누구인지 고려할 필요가 없다.


기저사례는 x==y인 경우로, 입력의 x번째 수가 홀수라면 1, 아니면 0을 리턴해주자.


이제 왼쪽을 고르거나 오른쪽을 고르는 경우를 모두 계산해주면 된다!


한 쪽을 고를 때 만들 수 있는 최대한의 점수차이는 어떻게 기술해야 할까?



한 쪽을 고르고 얻은 점수 - 나머지로 만드는 최대한의 점수 차이 = 쪽을 고르면 얻는 최대한의 점수차이


int left = func(x, x) - func((x + 1) % n, y);

int right = func(y, y) - func(x, (y + n - 1) % n);


라고 쓸 수 있다.


이걸 이해하는게 개인적으로 엄청 힘들었다 (멍청). 이번엔 턴을 고려하지 않는다.


내 턴이라 생각해보면, 선택 이후 턴이 넘어가기 때문에, 당연히 상대방은 최선의 전략을 구사하므로 함수를 호출하신다.

상대가 만드는 점수차는 당연히 내 점수에서 까야 된다.


왼쪽을 선택해서 만드는 점수차는 1점 - 3점차 = -2점차가 된다.



답은 어떻게 고를까? 내가 한 지점을 고르고 먹는 점수와, 상대방이 나머지로 만들 수 있는 최대 점수차를 비교해보면 된다.


func(i, i) - func((i + 1) % n, (i + n - 1) % n) > 0

이라면 내가 이긴다.



3. 코드


#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int dp[101][101];
int n, t, i;
int func(int x, int y) {
if (x == y)
return dp[x][y];
if (dp[x][y] != -1)
return dp[x][y];
int left = func(x, x) - func((x + 1) % n, y);
int right = func(y, y) - func(x, (y + n - 1) % n);
return dp[x][y] = max(left, right);
}
int main() {
scanf("%d", &n);
memset(dp, -1, sizeof(dp));
for (i = 0; i < n; ++i) {
scanf("%d", &dp[i][i]);
dp[i][i] %= 2;
}
int ans = 0;
for (i = 0; i < n; ++i)
if (func(i, i) > func((i + 1) % n, (i + n - 1) % n))
ans++;
printf("%d", ans);
return 0;
}


'알고리즘 > Dynamic Programming 동적계획법' 카테고리의 다른 글

백준) 9520 NP-hard (PUTNIK)  (0) 2017.08.24
백준) 10835 카드게임  (2) 2017.08.24
백준) 11062 카드게임(Card Game)  (1) 2017.08.23
백준) 9657 돌 게임 3  (0) 2017.08.18
백준) 9656 돌 게임 2  (0) 2017.08.17

11062. 카드 게임


문제

근우와 명우는 재미있는 카드 게임을 하고 있다. N개의 카드가 일렬로 놓여 있다. 각 카드에는 점수가 적혀있다. 근우부터 시작하여 번갈아가면서 턴이 진행되는데 한 턴에는 가장 왼쪽에 있는 카드나 가장 오른쪽에 있는 카드를 가져갈 수 있다. 카드가 더 이상 남아있지 않을 때까지 턴은 반복된다. 게임의 점수는 자신이 가져간 카드에 적힌 수의 합이다.

근우와 명우는 서로 자신의 점수를 가장 높이기 위해 최선의 전략으로 게임에 임한다. 놓여있는 카드의 개수 N과 카드가 놓여있는 상태가 주어졌을 때 근우가 얻는 점수를 구하는 프로그램을 작성하시오.

예를 들어 카드가 [4, 3, 1, 2]로 놓여있다고 하자. 근우는 처음에 4가 적힌 카드를 가져가고, 명우는 3이 적힌 카드를 가져간다. 그리고 근우는 2가 적힌 카드를 가져가고, 명우는 마지막으로 1이 적힌 카드를 가져간다. 이 때 근우와 명우는 최선의 전략으로 임했으며, 근우가 얻는 점수는 6이다.

입력

입력의 첫 줄에는 테스트케이스의 수 T가 주어진다.

각 테스트케이스 마다 첫 줄에는 카드의 개수 N(1 ≤ N ≤ 1,000)이 주어진다. 두 번째 줄에는 N개의 자연수가 공백으로 구분되어 주어지는데, i번째로 주어지는 수는 왼쪽에서 i번째에 놓인 카드에 적힌 수를 의미한다. 카드에 적혀있는 수는 1이상 10,000이하다.

출력

각 테스트케이스마다 근우와 명우가 최선의 전략으로 임할 때 근우가 얻게되는 점수를 줄로 구분하여 출력한다.

예제 입력 

2
4
1 2 5 2
9
1 1 1 1 2 2 2 2 2

예제 출력 

6
8


1. 접근


두 명이 일련의 규칙에 따라 게임을 진행한다.


단 둘다 최선의 전략을 알고 있다 가정할 때(최선의 전략이 뭔지는 아직 모른다),

선 턴인 주인공이 최대로 얻을 수 있는 점수는 몇 점일까?


계속되는 알고리즘 게임의 일종으로, 아무리 짱구를 굴려봐도 최선의 전략이 무엇일지는 감이 안오므로,

모든 경우를 계산해보는(하지만 빠른) 다이나믹 프로그래밍으로 풀어보자.



2. 풀이


dp[turn][남은 카드들 중 왼쪽 끝][남은 카드들 중 오른쪽 끝]으로 배열을 잡아보자.


턴은 번갈아 진행되며, 남은 카드들의 양 쪽 끝은 0 ~ n-1 까지다.

다이나믹 프로그래밍으로 한 번 기록된 dp배열에 접근하면 상수시간에 리턴되므로 배열 크기에 비례한 O(2*n*n)에 풀 수 있겠다.


이제 문제를 풀어줄 함수를 정의해보자.


func(int turn, int x, int y) = 턴이 turn이고, 왼쪽 끝은 x, 오른쪽 끝은 y 일 때 내가 얻을 수 있는 최대의 점수.


우리는 이제 기저사례로 x==y인 경우를 생각해 볼 수 있다.

남은 카드가 한 장이란 말로, 내 턴이였다면 점수를 취하고, 상대 턴이라면 "그냥 그랬구나" 하고 넘어가면 된다.


이제 왼쪽을 가져가든지, 오른쪽을 가져가든지 점수가 얼마나 될지 계산해봐야 한다.


내 턴이였다면 실제로 점수 계산을 위한 코드가 있어야 할테고,

상대 턴이였다면, 상대도 최선의 전략으로 플레이하기 때문에 양 쪽 플레이 중 나의 점수를 최소로 하는 플레이를 할 것이다.



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
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
 
#define my 0 
 
int dp[2][1001][1001];
int arr[1001];
int t, n, i;
 
int func(int turn, int x, int y) {
    if (x == y) {
        if (turn == my)
            return arr[x];
        else
            return 0;
    }
 
    int& ref = dp[turn][x][y];
    if (ref != -1)
        return ref;
 
    if (turn == my)
        ref = max(func(turn ^ 1, x + 1, y) + arr[x], func(turn ^ 1, x, y - 1+ arr[y]);
    else
        ref = min(func(turn ^ 1, x + 1, y), func(turn ^ 1, x, y - 1));
 
    return ref;
}
 
int main() {
    scanf("%d"&t);
    while (t--) {
        memset(dp, -1sizeof(dp));
 
        scanf("%d"&n);
 
        for (i = 0; i < n; ++i)
            scanf("%d"&arr[i]);
 
        printf("%d\n", func(my, 0, n - 1));
    }
 
    return 0;
}
cs


'알고리즘 > Dynamic Programming 동적계획법' 카테고리의 다른 글

백준) 10835 카드게임  (2) 2017.08.24
백준) 3032 승리(IVANA)  (0) 2017.08.23
백준) 9657 돌 게임 3  (0) 2017.08.18
백준) 9656 돌 게임 2  (0) 2017.08.17
백준) 9655 돌 게임  (0) 2017.08.17

9657. 돌 게임 3


문제

돌 게임은 두 명이서 즐기는 재밌는 게임이다.

탁자 위에 돌 N개가 있다. 상근이와 창영이는 턴을 번갈아가면서 돌을 가져가며, 돌은 1개, 3개 또는 4개 가져갈 수 있다. 마지막 돌을 가져가는 사람이 게임을 이기게 된다.

두 사람이 완벽하게 게임을 했을 때, 이기는 사람을 구하는 프로그램을 작성하시오. 게임은 상근이가 먼저 시작한다.

입력

첫째 줄에 N이 주어진다. (1 ≤ N ≤ 1000)

출력

상근이가 게임을 이기면 SK를, 창영이가 게임을 이기면 CY을 출력한다.

예제 입력 

6

예제 출력 

SK



1-1) 풀이 1


돌 게임 1(http://js1jj2sk3.tistory.com/30)과 비슷한 문제다. 4개도 가져갈 수 있다는 조건만 추가해주면 된다.


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 <string.h>
using namespace std;
 
#define my 1
#define your 0
#define win true
#define lose false
 
int n;
int dp[2][1001];
 
int func(int turn, int remain) {
    if (remain == 0) {
        if (turn == my)
            return dp[turn][remain] = lose;
        if (turn == your)
            return dp[turn][remain] = win;
    }
 
    if (remain < 0) {
        if (turn == my)
            return 1;
        if (turn == your)
            return 0;
    }
 
    if (dp[turn][remain] != -1)
        return dp[turn][remain];
 
    if (turn == my)
        dp[my][remain] =
        func(turn ^ 1, remain - 1| func(turn ^ 1, remain - 3| func(turn ^ 1, remain - 4);
 
    if (turn == your)
        dp[your][remain] =
        func(turn ^ 1, remain - 1& func(turn ^ 1, remain - 3& func(turn ^ 1, remain - 4);
 
    return dp[turn][remain];
}
 
int main() {
    scanf("%d"&n);
    memset(dp, -1sizeof(dp));
 
    if (func(1, n) == win)
        puts("SK");
    else
        puts("CY");
 
    return 0;
}
cs



1-2) 풀이 2 


0개라면 이미졌다.


1개면 이긴다.


2개면 진다.


3개면 이긴다.


4개면 이긴다.


까지가 기본적인 전략이고, http://js1jj2sk3.tistory.com/30 에서 설명한 추가 전략대로면,


5개면 완벽한 선택으로 상대에게 1, 3, 4 상태를 만들어 주지 않기 위해 3개를 가져가야 한다.


6개면 동일하게 4개를 가져가야 한다.


7개면 1개를 가져가든(나머지 6) 3개를 가져가든(나머지 4) 4개를 가져가든(나머지 3) 상대의 필승이다.


7개일 때부터는 이미 가져갈 수 있는 세 가지 전략에 대해 결과를 이미 알고 있다.


따라서 0개 ~ 6개의 결과가 반복될 것을 알 수 있다. (패/승/패/승/승/승/승)

'알고리즘 > Dynamic Programming 동적계획법' 카테고리의 다른 글

백준) 3032 승리(IVANA)  (0) 2017.08.23
백준) 11062 카드게임(Card Game)  (1) 2017.08.23
백준) 9656 돌 게임 2  (0) 2017.08.17
백준) 9655 돌 게임  (0) 2017.08.17
백준) 11066 파일 합치기  (6) 2017.07.09

9656. 돌 게임 2


문제

돌 게임은 두 명이서 즐기는 재밌는 게임이다.

탁자 위에 돌 N개가 있다. 상근이와 창영이는 턴을 번갈아가면서 돌을 가져가며, 돌은 1개 또는 3개 가져갈 수 있다. 마지막 돌을 가져가는 사람이 게임을 지게 된다.

두 사람이 완벽하게 게임을 했을 때, 이기는 사람을 구하는 프로그램을 작성하시오. 게임은 상근이가 먼저 시작한다.

입력

첫째 줄에 N이 주어진다. (1 ≤ N ≤ 1000)

출력

상근이가 게임을 이기면 SK를, 창영이가 게임을 이기면 CY을 출력한다.

예제 입력 

4

예제 출력 

SK




돌 게임 1(http://js1jj2sk3.tistory.com/30)의 결과만 반대로 출력해주면 되는 문제.



'알고리즘 > Dynamic Programming 동적계획법' 카테고리의 다른 글

백준) 3032 승리(IVANA)  (0) 2017.08.23
백준) 11062 카드게임(Card Game)  (1) 2017.08.23
백준) 9657 돌 게임 3  (0) 2017.08.18
백준) 9655 돌 게임  (0) 2017.08.17
백준) 11066 파일 합치기  (6) 2017.07.09

+ Recent posts