백준 9465번 스티커 문제 풀이 : https://www.acmicpc.net/problem/9465
문제요약 :
- 2xn개의 스티커를 갖고있다. 각 스티커는 점수가 있다.
- 스티커를 떼면 그 스티커와 변을 공유하는 스티커는 찢어져 사용할 수 없다(점수x)
- 뗄 수 있는 스티커 점수의 최댓값을 구하시오.
우선 이런 유형의 문제는 완전탐색을 하여 구현을 해봅니다.. 그다음 DP를 적용..
1. 완전탐색을 할 경우를 생각해봅니다.
"-->" 오른쪽 방향으로 탐색을 했을 때,
스티커를 떼는 방법은 위를 떼는 방법, 그리고 아래를 떼는 방법 두가지가 존재합니다.
우선 공유하는 변의 스티커가 뜯어지는 경우를 무시하고 전체 경우를 구한다면, 아래와 같이 구현할 수 있습니다.
1 2 3 4 5 6 7 8 | int solve(int x) { if (x >= n) return 0; int ret1 = solve(x + 1) + score[0][x];//위의 스티커를 뗀 경우, 위의 점수 저장 후 다음 줄로.. int ret2 = solve(x + 1) + score[1][x];//아래 스티커를 뗀 경우, 아래 점수 저장 후 다음 줄로.. return MAX(ret1, ret2); } | cs |
그러나 위의 문제는 현재 스티커를 뜯었을 경우, 변을 공유하고 있는 스티커는 뜯을 수가 없다..
즉, 현재 스티커를 뜯은 경우에 따라 다음 선택에 영향을 주는 것이다.
우선 함수 자체에 바뀐 스티커의 상태를 알려줄 변수를 추가 후 상태에 따라 다른 스티커를 뜯게한다.
+그리고 현재 상태에서 둘다 뜯지 않은 경우를 추가한다. (이 경우 다음에서 위/아래 둘다 뜯을수가 있다.)
코드로 보면,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | int solve(int x, int state)//현재 선택할 수 있는 스티커를를 알려줄 state변수 추가 { if (x >= n) return 0; int ret; switch (state) { case 0://위의 스티커를 뜯을 수 있다. ret = solve(x + 1, 1) + score[0][x];//위에를 뜯었으니 다음은 아래를 뜯을 수 있다고 알려준다. break; case 1://아래 스티커를 뜯을 수 있다. ret = solve(x + 1, 0) + score[1][x];//아래를 뜯었으니 다음은 위를 뜯을 수 있다고 알려준다. break; case 2://둘다 뜯을 수 있는 경우, 이전에 아무것도 뜯지 않은 경우이다. ret = solve(x + 1, 1) + score[0][x]; ret = MAX(ret, solve(x + 1, 0) + score[1][x]); break; } ret = MAX(ret, solve(x + 1, 2));//아무것도 뜯지 않은 경우. 점수를 더하지 않고 2전달. return ret; } | cs |
2. DP를 적용합니다.
이렇게 하면 완전탐색으로 구할 수 있습니다.. 하지만 여기까지만 하면 시간초과가 발생.. DP를 적용해야 합니다.
여기까지 했다면, DP를 적용하는건 쉽습니다. x까지 구한 최댓값을 state에 따라 값을 저장하면 됩니다.
아래 음영과같이 해주면 됩니다.
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 | int DT[3][100001]; int solve(int x, int state)//현재 선택할 수 있는 스티커를를 알려줄 state변수 추가 { if (x >= n) return 0; if (DT[state][x] != -1) return DT[state][x]; int ret; switch (state) { case 0://위의 스티커를 뜯을 수 있다. ret = solve(x + 1, 1) + score[0][x];//위에를 뜯었으니 다음은 아래를 뜯을 수 있다고 알려준다. break; case 1://아래 스티커를 뜯을 수 있다. ret = solve(x + 1, 0) + score[1][x];//아래를 뜯었으니 다음은 위를 뜯을 수 있다고 알려준다. break; case 2://둘다 뜯을 수 있는 경우, 이전에 아무것도 뜯지 않은 경우이다. ret = solve(x + 1, 1) + score[0][x]; ret = MAX(ret, solve(x + 1, 0) + score[1][x]); break; } ret = MAX(ret, solve(x + 1, 2));//아무것도 뜯지 않은 경우. 점수를 더하지 않고 2전달. return DT[state][x] = ret; } | cs |
전체 코드는 아래와 같습니다.
저는 단계별로 구현하는 과정을 설명하고 있는데요, 처음부터 DP를 이용해 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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int n; int score[2][100001]; int DT[3][100001]; int MAX(int a, int b) { return (a > b) ? a : b; } int solve(int x, int state) { if (x >= n) return 0; if (DT[state][x] != -1) return DT[state][x]; int ret; switch (state) { case 0: ret = solve(x + 1, 1) + score[0][x]; break; case 1: ret = solve(x + 1, 0) + score[1][x]; break; case 2: ret = solve(x + 1, 1) + score[0][x]; ret = MAX(ret, solve(x + 1, 0) + score[1][x]); break; } ret = MAX(ret, solve(x + 1, 2)); return DT[state][x] = ret; } int main(int argc, char **argv) { int testcase; scanf("%d", &testcase); for (int tc = 0; tc < testcase; tc++) { scanf("%d", &n); for (int i = 0; i < 2; i++) for (int j = 0; j < n; j++) scanf("%d", &score[i][j]); for (int i = 0; i < n; i++) for (int j = 0; j < 3; j++) DT[j][i] = -1; int max = 0; for (int i = 0; i<= 2; i++) max = MAX(max, solve(0, 2)); printf("%d\n", max); } return 0; } | cs |
'Software Development > Algorithm' 카테고리의 다른 글
[자료구조] 동적할당을 이용한 연결 리스트(Linked List) 구현 (0) | 2017.03.05 |
---|---|
[DP] 기업투자 문제 풀이 (1) | 2017.01.23 |
[DP] 0/1 Knapsack(배낭) 문제 (5) | 2016.07.24 |
Dynamic Programming (2) | 2016.07.02 |
알고리즘_타일 채우기 문제 (2) | 2016.03.06 |