백준의 줄세우기(2631) 문제이다.
[ 문제 바로가기 ]
[ 문제풀이 ]
1) 풀이보다는 문제의 해결방법을 찾는데 너무 시간이 오래 걸렸던 문제이다. 어떤식으로 접근했는지 차근차근 알아가보자.
쉬운 예를 들어서 알아보자. 예를 들어, { 1, 2, 3, 4, 5 } 순서로 줄을 서 있는 아이들이 있다고 가정해보자. 이 아이들의 경우
아이들이 애초에 순서대로 서있기 때문에 더 이상 옮겨주지 않아도 된다. 즉, 답은 0이 된다.
그렇다면 { 5, 4, 3, 2, 1 } 의 경우는 어떻게 될까? 2,3,4,5 번 아이들을 순서대로 옮기면 순서에 맞게 줄이 이루어 지므로
답은 4가 된다. 본인은 여기서 "가장 큰 증가하는 부분수열" 이 생각났다. Dynamic Programming의 방식으로 접근하면서 주어진
수열에서 가장 큰 증가하는 부분수열의 크기만큼을 전체크기에서 빼주면 정답이 나올 것 같다는 생각이 들었다.
왜냐하면, 하나의 수열에 속한 부분수열 중에서 가장 큰 증가하는 부분수열을 기준점으로 잡아버린다면, 나머지 아이들만
옮겨주면 정답이 나올 것 같았다. 또한, 위에서 예로 든 { 1, 2, 3, 4, 5 } 의 경우, 가장 큰 증가하는 부분수열의 크기는 5이다.
전체 5명에서 5를 빼버리면 0이라는 결과를 도출할 수 있었고, { 5, 4, 3, 2, 1 }의 경우 가장 큰 증가하는 부분수열의 크기는 1이기
때문에 전체 5명에서 1을 뺀 4명을 옮기면 결국 답이 4가 되어 이런 방법을 생각해 보았다.
실제로 예제입력1인 { 3, 7, 5, 2, 6, 1, 4 } 에서 가장 큰 증가하는 부분수열의 크기는 { 3, 5, 6 } 으로 3이되고, 전체 7 에서 3을
빼면 4가 나오게 된다.
2) 그렇다면 가장 큰 증가하는 부분수열을 어떻게 구할지 알아보자.
가장 큰 증가하는 부분수열이 어떤 수열을 말하는 것인지부터 차근차근 알아보자. 주어진 수열에서 부분 수열인데, 그 수열의
규칙이 점점 증가하는 오름차순으로만 이루어진 수열을 의미한다.
증가하는 부분수열을 구하는 방법은 이렇다.
1. 자기보다 이전에 있는 값들 중에서 더 작은 값들을 모두 비교한다.
2. 더 작은 값들 중, 해당 값을 선택했을 때, 증가하는 부분수열의 크기가 기존의 값보다 더 커지는지 비교한다.
3. 2)의 결과로 기존의 값보다 더 커진다면, 그 값으로 갱신, 그게 아니라면 pass.
본인은, 이 증가하는 부분수열의 크기를 저장하기 위해서 DP[] 라는 1차원 배열을 사용해주었다.
DP[A] = B의 의미는 "A번 인덱스에 있는 값을 선택했을 때, 증가하는 부분수열의 크기는 B입니다." 가 된다.
{ 3, 7, 5, 4, 6, 2 } 라는 순열이 있다고 생각해보자. (제일 앞에서부터 1 ~ 6번 인덱스라고 가정)
먼저 제일 앞에 '3'을 보자. 3보다 이전에 있는 값은 존재하지 않기 때문에, '3'일 때 가장 큰 증가하는 부분수열의 크기는
1이 된다. 1이 되는 이유는 '3' 하나만으로도 부분수열이 되기 때문에 초기값은 1이 된다. DP[1] = 1.
그다음 '7'을 보자. 7보다 이전에 있는 값들 중 더 작은 값으로는 '3'이 있고, '3'을 선택했을 때, DP[2]의 기존의 값인
1보다 더 큰 값이 되기 때문에, '3'을 선택한다. 즉, DP[2] = 2가 된다. { 3, 7 } !
'5'를 보자. 이전에 있는 값들 중 더 작은 값으로는 '3'이 있고, 이 값을 선택함으로써 DP[3]의 크기가 더 커질 수 있으므로 갱신 !
DP[3] = 2.
'4'의 경우, 윗줄에서 말한 '5'와 마찬가지로, { 3, 4 } 로 DP[4] = 2가 된다.
'6'을 보자. 6보다 이전에 있는 값들 중 더 작은 값인 { 3, 5, 4 } 중에서, '3'을 선택한 경우를 생각해보자.
현재 DP[5] = 1이다.('6'은 5번째 Index값) '3'을 선택하는 순간, DP[5] = 2가 된다. { 3, 5 } 가 되기 때문이다,.
'5'를 선택하게 되면, DP[5] = 3이 된다. 결과적으로 보자면 { 3, 5, 6 }이 되기 때문이다.
그렇다면, 여기서 값을 어떻게 비교하는지 체크해보자. DP[3]의 값은 2이다.('5'는 3번 Index값)
그렇다면, DP[3] + 1의 값과, DP[5]의 값을 비교하게 된다.
갑자기 무슨 말도 안되는 소리가 튀어나왔다는거 잘 알고있다. 현재 DP[3]은 2라는 값을 가지고 있다. 즉, 3번인덱스에서 가장 큰
증가하는 부분수열의 크기는 2 입니다. 라는 의미를 가지고 있는데, '5'번 인덱스에서의 최대길이를 구하기 위해서
'3'번 인덱스를 선택하게 되면, '3'번 인덱스의 최대길이 + 1이 되기 떄문이다.
잘 생각을 해보자. 3번 인덱스인 '5'에서 최대길이는 2이다. 여기서 가장 뒤에 6이 붙으면 길이는 3이 될 것이다.
{ 3, 5 } -> { 3, 5, 6 } 이렇게 ! 즉, 5번 인덱스인 '6'에서 최대길이를 구하는 과정에서, 3번인덱스인 '5'의 값을 고르게 되면
이 때, 5번 인덱스의 길이는 3번인덱스의 값 + 1 이된다.
최대길이를 이런식으로 구할 수 있게 된다. 아래의 코드를 보면서 정리를 한번 해보자.
for (int i = 1; i <= N; i++) // 1부터 N번 인덱스까지 반복한다.
{
DP[i] = 1; // 증가하는 부분수열의 최대길이의 초기값은 1
for (int j = 1; j <= i; j++) // 1. 이전에 있는 값들을 확인한다.
{
if (Arr[j] < Arr[i] && DP[i] < DP[j] + 1) // 더 작은 값이면서, 해당 인덱스를 골랐을 때
{ // 1증가한 크기가 기존의 값보다 크다면
DP[i] = DP[j] + 1; // 해당 인덱스를 선택하고 크기 갱신
}
}
if (Max < DP[i]) Max = DP[i]; // 최대 증가하는 부분수열의 크기값 저장
}
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 47 48 49 50 51 52 53 54 55 | #include<iostream> #define endl "\n" #define MAX 200 + 1 using namespace std; int N, Max; int Arr[MAX]; int DP[MAX]; void Input() { cin >> N; for (int i = 1; i <= N; i++) { cin >> Arr[i]; } } void Solution() { for (int i = 1; i <= N; i++) { DP[i] = 1; for (int j = 1; j <= i; j++) { if (Arr[j] < Arr[i] && DP[i] < DP[j] + 1) { DP[i] = DP[j] + 1; } } if (Max < DP[i]) Max = DP[i]; } cout << N - Max << endl; } void Solve() { Input(); Solution(); } int main(void) { ios::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); // freopen("Input.txt", "r", stdin); Solve(); return 0; } | cs |
'[ BOJ Code ] > # BOJ -' 카테고리의 다른 글
[ 백준 1052 ] 물병 (C++) (0) | 2019.03.11 |
---|---|
[ 백준 16918 ] 봄버맨 (C++) (5) | 2019.03.11 |
[ 백준 2151 ] 거울 설치 (C++) (2) | 2019.03.10 |
[ 백준 2186 ] 문자판 (C++) (6) | 2019.03.10 |
[ 백준 1197 ] 최소 스패닝 트리 (C++) (5) | 2019.03.04 |