알고리즘/알고리즘 문제

[백준] 18353 병사 배치하기

천니 2021. 9. 19. 13:17
728x90

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

 

18353번: 병사 배치하기

첫째 줄에 N이 주어진다. (1 ≤ N ≤ 2,000) 둘째 줄에 각 병사의 전투력이 공백을 기준으로 구분되어 차례대로 주어진다. 각 병사의 전투력은 10,000,000보다 작거나 같은 자연수이다.

www.acmicpc.net

문제를 풀다가 못 풀겠어서 풀이를 봤다... 최장 길이 부분수열(LIS) 문제와 유사한 문제로 개념이 있어야 쉽게 풀리는 문제였다. 풀이는 생각보다 간단하여 금방 풀었다. 

 

최장 길이 부분수열 (LIS) 를 푸는데 2가지 방법이 있다. 

 

첫 번째, 시간 복잡도가 O(N제곱)으로 푸는 방법.

두 번째, 시간 복잡도가 O(NlogN)으로 푸는 방법. 

 

두 가지 모두 살펴보고자 한다.

 

 

첫 번째, 내가 이 문제를 풀었던 O(N제곱) 방법이다. 푸는데 가장 간단한? 방법이라 생각한다. DP를 이용하는 방법으로 

 

for (int i = 0; i < N; i++) {
		brr[i] = 1;
	}

	int max_num = 0;
	for (int i = 1; i < N; i++) {
		for (int j = 0; j < i; j++) {
			if (arr[i] < arr[j]) {
				brr[i] = max(brr[i], brr[j] + 1);
			}
		}
	}

코드에 한 부분을 떼어왔다.

 

주어진 배열에서 인덱스를 한 칸씩 늘려가면서 확인하는 방법이다. 만약에 값 중에 arr[i] < arr[k]인 것이 있을 경우, brr 를 업데이트하는 것이다. 즉 자신보다 이전에 있던 배열의 값이 클 경우만 +1을 해주는 것이다. 

 

이렇게 2중 배열문을 완성하면 최장 길이를 알 수 있다 .

 

 

두 번째, 이분탐색을 이용한 방법이다.

 

n의 크기가 클 경우 O(NlogN) 의 시간복잡도를 갖기 때문에 O(N제곱)인 위의 방식보다 훨씬 효과적이다.

이 방식은 두 개의 배열이 필요하다. 편의상 arr, lis 배열이라고 하자

 

arr배열이 lis 배열의 가장 마지막 원소와 비교했을 때

 

- 증가하는 부분 수열일 경우, LIS 배열의 가장 끝에 추가한다.

 

- 증가하는 부분 수열이 아닐 경우, LIS배열을 이분 탐색하여 들어갈 위치를 찾아 값을 대체한다.

        (Lower_bound 함수를 이용하여 편하게 구하자)

 

이 부분을 arr 배열의 끝의 원소에 도달할 때까지 반복하자

LIS 배열의 마지막 원소가 있는 인덱스가 가장 긴 부분 증가 수열의 길이가 된다.

 

(※여기서 중요한 점은 이 알고리즘이 끝난 후 lis 배열의 원소 개수가 최대 증가수열 값을 의미하는 것이지 그 배열 자체가 최대 증가수열을 말하지는 않는다!)


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

using namespace std;

int brr[20000];

int main() {
	int N;
	cin >> N;
	
	vector<int> arr;

	for (int i = 0; i < N; i++) {
		int num;
		cin >> num;
		arr.push_back(num);
	}
	
	for (int i = 0; i < N; i++) {
		brr[i] = 1;
	}

	int max_num = 0;
	for (int i = 1; i < N; i++) {
		for (int j = 0; j < i; j++) {
			if (arr[i] < arr[j]) {
				brr[i] = max(brr[i], brr[j] + 1);
			}
		}
	}



	for (int i = 0; i < N; i++) {
		cout << brr[i];
		max_num = max(brr[i], max_num);
	}
	cout << N - max_num;
}
댓글수0