프로그래밍/기타 문제풀이

[BOJ 12015] 가장 긴 증가하는 부분수열 2

kkalgo 2022. 11. 9. 18:32

 

 

12015번: 가장 긴 증가하는 부분 수열 2

첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000,000)이 주어진다. 둘째 줄에는 수열 A를 이루고 있는 Ai가 주어진다. (1 ≤ Ai ≤ 1,000,000)

www.acmicpc.net

수열의 크기가 1,000,000으로 

가장 쉬운 방법인 O(n^2)의 방식으로 증가수열의 길이를 구하기에는 시간초과가 나는 문제입니다.

 

따라서 최소 O(NlogN)로 동작하는 알고리즘을 설계해야 합니다.

이분탐색을 이용하여 증가수열의 길이를 구하는 방식이 존재합니다.

단, 이 방식으로는 수열의 길이만 알 수 있고 수열은 알 수 없습니다. 물론, 수열을 알고 싶다면 추가로 수정을 하면 됩니다.

 

우선 문제의 예시로 증가수열을 설명하겠습니다.

10 20 10 30 20 50
1 2 1 3 2 4

[10 20 10 30 20 50] --> [10,20,30,50] (길이 4)

 

* [10, 20, 10, 30, 20, 50]

처음 arr에는 10이 들어가 있는 상태로,

그 이후부터 들어갈 원소와 arr에 들어있는 '마지막 원소'와 비교를 시작합니다.

1) '마지막 원소'보다 큰 경우에는 arr에 삽입

2) '마지막 원소'보다 작은 경우에는 arr 배열 내에서 들어갈 원소보다 크거나 같은 값 중 가장 작은 값과 변경

--> 이 방식이 '길이'에는 아무런 영향을 주지 않습니다.

 

맨 처음 arr[0] = 10,

 

1) 20이 들어온 경우 arr[0] < 20이므로 arr에 삽입, arr의 마지막 원소는 arr[1]인 20이 되었음.

2) 10이 들어온 경우 arr[1] > 10이므로 arr에 삽입하지 않고, 10보다 크거나 같은 수 중 가장 작은 수가 있는

     arr[0]의 값을 10으로 변경해줌.   (arr의 사이즈는 여전히 2)

3) 30이 들어온 경우 arr[1] < 30이므로 arr에 삽입, arr의 마지막 원소는 arr[2]인 30이 되었음.

4) 20이 들어온 경우 arr[2] > 20이므로 arr에 삽입하지 않고, 20보다 크거나 같은 수 중 가장 작은 수가 있는

     arr[1]의 값을 20으로 변경해줌.  (arr의 사이즈는 여전히 3)

5) 50이 들어온 경우 arr[2] < 50이므로 arr에 삽입, arr의 마지막 원소는 arr[3]인 50이 되었음.

 

이 때의 arr의 size가 증가수열의 길이가 됩니다.

 

 

[7 1 5 2 3]의 입력으로 예시를 하나 더 들어보겠습니다.

 

맨 처음 arr[0] = 7,

1) 1이 들어온 경우 arr[0] > 1이므로 arr에 삽입하지 않고, 1보다 크거나 같은 수 중 가장 작은 수가 있는

      arr[0]의 값을 1로 변경해줌. 

2) 5가 들어온 경우 arr[0] < 5이므로 arr에 삽입. arr의 마지막 원소는 arr[1]인 5가 되었음.

3) 2가 들어온 경우 arr[1] > 2이므로 arr에 삽입하지 않고, 2보다 크거나 같은 수 중 가장 작은 수가 있는

       arr[1]의 값을 2로 변경해줌.

4) 3이 들어온 경우 arr[1] < 3이므로 arr에 삽입, arr의 마지막 원소는 arr[2]인 3이 되었음.

 

이 때의 arr의 size가 증가수열의 길이가 됩니다.

 

위의 방식은 증가수열의 형태는 다를 수 있어도 길이는 항상 최대를 보장합니다.

(1, 2, 4)와 (3,5,7)의 길이는 같습니다.

증가수열을 만드는 입장에서 항상 유리한 상태는 '마지막 원소'가 최대한 작은 상태가 유리합니다.

삽입될 원소가 마지막 원소보다 크기만 하다면 유리한데,

[ 3 5 4 7 ] '길이'의 입장에서 [3,5,7]과 [4,5,7]은 동일합니다.

여기서 3을 4로 바꿔준 의미는 단순히, '4'에서도 최장 증가수열의 길이의 가능성이 있기 때문에

바꿔준 것일 뿐이고 이 상태는 '3'에서 시작한 증가수열의 길이를 아직 가지고 있습니다.

[ 3 5 4 7 1 2 3 4 ] 

이런 식으로 들어왔을 경우

[4 5 7] -> [1 2 3 4] 로 변경의 가능성이 있기 때문에 들어온 수보다 큰 값중 가장 작은 값을 게속 갱신하여

변경해주는 것입니다.

[4 5 7] -> [1 2 3 4] 로 변경이 되면서 세번 째 4의 증가수열 가능성은 이미 벗어나버렸음을 갱신.

 

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

vector<int> arr;

int main(void)
{
	int n;
	cin >> n;
	int temp;
	cin >> temp;
	arr.push_back(temp);
	int idx = 1;

	for (int i = 2; i <= n; i++)
	{
		cin >> temp;
		if (temp > arr[idx - 1])
		{
			arr.push_back(temp);
			idx++;
		}
		else
		{
			int id = lower_bound(arr.begin(), arr.end(), temp) - arr.begin();
			arr[id] = temp;
		}
	}

	cout << idx;
}