백준 9465 C++ 스티커

PS / / 2021. 2. 7. 10:17

동적계획법으로 최댓값을 구하는 전형적인 유형이었다.

C++에서 제공하는 max 함수를 이용하면 간단하게 해결할 수 있다!

 

시간 제한 메모리 제한 정답률
1초 256MB 49.014%

 

문제

상근이의 여동생 상냥이는 문방구에서 스티커 2n개를 구매했다. 스티커는 그림 (a)와 같이 2행 n열로 배치되어 있다. 상냥이는 스티커를 이용해 책상을 꾸미려고 한다.

상냥이가 구매한 스티커의 품질은 매우 좋지 않다. 스티커 한 장을 떼면, 그 스티커와 변을 공유하는 스티커는 모두 찢어져서 사용할 수 없게 된다. 즉, 뗀 스티커의 왼쪽, 오른쪽, 위, 아래에 있는 스티커는 사용할 수 없게 된다.

모든 스티커를 붙일 수 없게된 상냥이는 각 스티커에 점수를 매기고, 점수의 합이 최대가 되게 스티커를 떼어내려고 한다. 먼저, 그림 (b)와 같이 각 스티커에 점수를 매겼다. 상냥이가 뗄 수 있는 스티커의 점수의 최댓값을 구하는 프로그램을 작성하시오. 즉, 2n개의 스티커 중에서 점수의 합이 최대가 되면서 서로 변을 공유 하지 않는 스티커 집합을 구해야 한다.

위의 그림의 경우에 점수가 50, 50, 100, 60인 스티커를 고르면, 점수는 260이 되고 이 것이 최대 점수이다. 가장 높은 점수를 가지는 두 스티커 (100과 70)은 변을 공유하기 때문에, 동시에 뗄 수 없다.

입력

첫째 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스의 첫째 줄에는 n (1 ≤ n ≤ 100,000)이 주어진다. 다음 두 줄에는 n개의 정수가 주어지며, 각 정수는 그 위치에 해당하는 스티커의 점수이다. 연속하는 두 정수 사이에는 빈 칸이 하나 있다. 점수는 0보다 크거나 같고, 100보다 작거나 같은 정수이다. 

출력

각 테스트 케이스 마다, 2n개의 스티커 중에서 두 변을 공유하지 않는 스티커 점수의 최댓값을 출력한다.

 

핵심

  • dp[n][l] | n:왼쪽에서부터 n번째까지, l:n번째 열에서 아무것도 안떼는지, 윗 스티커를 떼는지, 아래 스티커를 떼는지 dp[n][l] : 그 상황에서 점수의 최댓값

코드

#include <iostream>
#include <algorithm>


using namespace std;

int t, n;



int main(int argc, char* argv[]) {
	ios_base :: sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    
    cin >> t;
    
    while(t--){ // t번의 테스트 케이스를 진행하기 위해
        int answer;
        int tmp_answer; // dp를 채워가면서 각 상황에서의 최댓값을 저장하는 변수
        int sticker[100010][3]; //sticker 점수 정보 저장, sticker[n][1] : 위쪽 스티커 점수, sticker[n][2] : 아래쪽 스티커 점수
        int dp[100010][3]; 
        /* dp table
            dp[n][0] : 마지막 줄 스티커를 떼지 않은 상황에서 점수 최대값
            dp[n][1] : 마지막 줄 위 스티커를 뗀 상황에서 점수 최대값
            dp[n][2] : 마지막 줄 아래 스티커를 뗀 상황에서 점수 최대값
        */
        
        cin >> n;
        
        for(int i=1;i<=n;i++){
            cin >> sticker[i][1]; // sticker 점수 정보 받기(점수 입력 순서를 조심해야 한다. 첨에 위에서 아래방향으로 받는걸로 실수한 덕분에 10분 날림ㅠ)
        }
        for(int i=1;i<=n;i++){
            cin >> sticker[i][2]; // sticker 점수 정보 받기
        }
        
        
        dp[1][0] = 0;
        dp[1][1] = sticker[1][1];
        dp[1][2] = sticker[1][2]; // 초항 설정, 열이 하나인데 마지막 줄 아래 스티커를 뗀 상황이라면 그냥 그 열의 아래쪽 스티커만 뗀 상황이다.
        
        
        for(int i=2;i<=n;i++){
            dp[i][0] = max(dp[i-1][0], max(dp[i-1][1], dp[i-1][2]));    //마지막 줄 스티커를 안 뗄 것이라면, 그냥 전 항의 값 중 가장 큰 값을 받으면 된다.
            dp[i][1] = max(dp[i-1][0], dp[i-1][2]) + sticker[i][1];    //마지막 줄 윗 스티커를 뗄 것이라면, 전 줄에서 아무것도 안 뗀 것 vs 전 줄 아래 스티커 뗀 것 중 큰 값을 받는다.
            dp[i][2] = max(dp[i-1][0], dp[i-1][1]) + sticker[i][2];    //마지막 줄 아래 스티커를 뗄 것이라면, 전 줄에서 아무것도 안 뗀 것 vs 전 줄 윗 스티커 뗀 것 중 큰 값을 받는다.
        }
        
        answer = max(dp[n][0], max(dp[n][1], dp[n][2]));
        
        cout << answer << '\n';
    }
	
	return 0;
}

해설

왼쪽 첫번째 열부터 1열, 2열, ... i열, ..., n열 으로 생각하자.

n=1이라면 위 스티커나 아래 스티커 중 큰 걸 떼면된다.

n>=2라면 직전 열에서 어떤 스티커를 떼느냐에 따라 맨 오른쪽 열에서 무슨 스티커를 뗄 수 있는지가 결정이 된다.

여기서 주의할 점은 무조건 많이 뗀다고 최댓값이 되는 것이 아니라는 것이다.

(i-1)열이 10점, 20점밖에 안되는데 k열이 100점, 99점이고, (i-2)열이 98점, 97점이라면 (i-1)열은 되도록 안 건드리는 것이 좋을 것이다.

(건드리면 그 전열과 후열 스티커를 선택하는데 제약이 생기기 때문에)

따라서, k열에서 스티커를 떼지 않는 경우(case 0), 위쪽 스티커를 떼는 경우(case 1), 아래쪽 스티커를 떼는 경우(case 2) 이 세 가지를 나눠서 동적계획법 알고리즘을 적용해야 한다.

 

i열까지의 최댓값을 구하려면,

  • case 0 : (i-1) case 0 vs (i-1) case 1 vs (i-1) case 2
  • case 1 : {(i-1) case0 vs (i-1) case 2} + i열 윗 스티커점수 (i열 윗 스티커를 떼려면 (i-1)열에서 아무것도 안떼거나 아래 스티커만 떼야 하므로)
  • case 2 : {(i-1) case0 vs (i-1) case 1} + i열 아래 스티커점수 (i열 아래 스티커를 떼려면 (i-1)열에서 아무것도 안떼거나 윗 스티커만 떼야 하므로)

이 중에서 가장 큰 값을 구하면 되고, 이를 바탕으로 점화식을 세우면 위 코드를 작성할 수 있다.

 

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기