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

문제를 효율적이고 간결하게 해결하기 위해 동적 계획법(Dynamic Programming, DP)을 활용한 접근 방식을 사용하겠습니다. 이 접근 방식은 최장 감소 부분 수열(Longest Decreasing Subsequence, LDS)을 찾아, 이를 기반으로 필요한 최소 열외 병사의 수를 계산하는 방법입니다.
N명의 병사가 무작위로 나열되어 있으며, 이들을 전투력이 높은 순서대로 내림차순으로 배치하기 위해 최소한의 병사를 열외시키는 방법을 찾습니다. 최종적으로 남아있는 병사의 수가 최대가 되도록 해야 합니다.N (1 ≤ N ≤ 2,000)N개의 병사의 전투력 (1 ≤ 전투력 ≤ 10,000,000), 공백으로 구분예제 입력 1:
7
15 11 4 8 5 2 4
예제 출력 1:
2
해석:
[15, 11, 8, 5, 4]로 내림차순이 됩니다.2입니다.이 문제는 최장 감소 부분 수열(Longest Decreasing Subsequence, LDS)을 찾는 문제로 변환할 수 있습니다. LDS는 주어진 수열에서 연속되지 않은 원소들을 선택하여, 그 수열이 내림차순이 되도록 하는 가장 긴 부분 수열을 의미합니다.
핵심 아이디어:
N에서 LDS Length를 뺀 값입니다.N과 각 병사의 전투력 리스트를 입력받습니다.dp[i]를 정의합니다: i번째 병사를 마지막으로 하는 LDS의 길이.i에 대해, 이전 병사 j (j < i) 중 전투력 A[j]가 A[i]보다 큰 경우를 찾아, dp[i]를 업데이트합니다.dp[i] = max(dp[i], dp[j] + 1) (단, A[j] > A[i])dp[i] 중 최댓값을 찾아 LDS Length를 구합니다.N - LDS Length입니다.O(N^2)O(N)dp 배열을 사용하여 각 병사에 대한 LDS Length를 저장합니다.아래는 위의 접근 방식을 구현한 파이썬 코드입니다.
def longest_decreasing_subsequence(N, combat_powers):
# DP 배열 초기화: 각 위치에서의 LDS 길이를 저장
dp = [1] * N
for i in range(1, N):
for j in range(i):
if combat_powers[j] > combat_powers[i]:
if dp[j] + 1 > dp[i]:
dp[i] = dp[j] + 1
# LDS의 최대 길이
lds_length = max(dp)
# 열외해야 하는 병사의 수
soldiers_to_remove = N - lds_length
return soldiers_to_remove
def main():
import sys
input = sys.stdin.readline
# 병사의 수 N 입력
N_line = ''
while N_line.strip() == '':
N_line = input()
N = int(N_line.strip())
# 병사의 전투력 입력
combat_powers = []
while len(combat_powers) < N:
line = input()
if not line:
break
parts = line.strip().split()
combat_powers += list(map(int, parts))
# 결과 계산
result = longest_decreasing_subsequence(N, combat_powers)
print(result)
if __name__ == "__main__":
main()
longest_decreasing_subsequence(N, combat_powers)N: 병사의 수combat_powers: 병사들의 전투력 리스트int)dp: 각 병사를 마지막으로 하는 LDS의 길이를 저장하는 리스트lds_length: 전체 병사 중 LDS의 최대 길이soldiers_to_remove: 필요한 열외 병사의 수dp = [1] * N
for i in range(1, N):
for j in range(i):
if combat_powers[j] > combat_powers[i]:
if dp[j] + 1 > dp[i]:
dp[i] = dp[j] + 1
dp를 1로 초기화합니다.i에 대해, 이전 병사 j (0 ≤ j < i) 중 전투력 combat_powers[j]가 combat_powers[i]보다 큰 경우를 찾아, dp[i]를 업데이트합니다.i를 포함한 LDS의 길이를 갱신하는 과정입니다.lds_length = max(dp)
soldiers_to_remove = N - lds_length
return soldiers_to_remove
dp[i] 중 최댓값을 찾아 LDS의 최대 길이를 구합니다.N에서 LDS의 길이를 뺀 값입니다.def main():
import sys
input = sys.stdin.readline
# 병사의 수 N 입력
N_line = ''
while N_line.strip() == '':
N_line = input()
N = int(N_line.strip())
# 병사의 전투력 입력
combat_powers = []
while len(combat_powers) < N:
line = input()
if not line:
break
parts = line.strip().split()
combat_powers += list(map(int, parts))
# 결과 계산
result = longest_decreasing_subsequence(N, combat_powers)
print(result)
N을 입력받습니다.N개의 전투력을 입력받아 combat_powers 리스트에 저장합니다.longest_decreasing_subsequence 함수를 호출하여 필요한 열외 병사의 수를 계산합니다.예제 입력 1:
코드 복사
7
15 11 4 8 5 2 4
처리 과정:
dp = [1, 1, 1, 1, 1, 1, 1]i = 0: 이미 초기화됨i = 1j = 0: 15 > 11 → dp[1] = max(1, 1 + 1) = 2dp = [1, 2, 1, 1, 1, 1, 1]i = 2j = 0: 15 > 4 → dp[2] = max(1, 1 + 1) = 2j = 1: 11 > 4 → dp[2] = max(2, 2 + 1) = 3dp = [1, 2, 3, 1, 1, 1, 1]i = 3j = 0: 15 > 8 → dp[3] = max(1, 1 + 1) = 2j = 1: 11 > 8 → dp[3] = max(2, 2 + 1) = 3j = 2: 4 > 8 → 조건 불만족dp = [1, 2, 3, 3, 1, 1, 1]i = 4j = 0: 15 > 5 → dp[4] = max(1, 1 + 1) = 2j = 1: 11 > 5 → dp[4] = max(2, 2 + 1) = 3j = 2: 4 > 5 → 조건 불만족j = 3: 8 > 5 → dp[4] = max(3, 3 + 1) = 4dp = [1, 2, 3, 3, 4, 1, 1]i = 5j = 0: 15 > 2 → dp[5] = max(1, 1 + 1) = 2j = 1: 11 > 2 → dp[5] = max(2, 2 + 1) = 3j = 2: 4 > 2 → dp[5] = max(3, 3 + 1) = 4j = 3: 8 > 2 → dp[5] = max(4, 3 + 1) = 4j = 4: 5 > 2 → dp[5] = max(4, 4 + 1) = 5dp = [1, 2, 3, 3, 4, 5, 1]i = 6j = 0: 15 > 4 → dp[6] = max(1, 1 + 1) = 2j = 1: 11 > 4 → dp[6] = max(2, 2 + 1) = 3j = 2: 4 > 4 → 조건 불만족j = 3: 8 > 4 → dp[6] = max(3, 3 + 1) = 4j = 4: 5 > 4 → dp[6] = max(4, 4 + 1) = 5j = 5: 2 > 4 → 조건 불만족dp = [1, 2, 3, 3, 4, 5, 5]최종 dp 배열: [1, 2, 3, 3, 4, 5, 5]
LDS Length: 5 (예: [15, 11, 8, 5, 2] 또는 [15, 11, 8, 5, 4] 등)
열외해야 하는 병사의 수: 7 - 5 = 2
출력:
2
DP 배열 초기화:
dp = [1] * NO(N)최장 감소 부분 수열 계산:
for i in range(1, N):
for j in range(i):
if combat_powers[j] > combat_powers[i]:
if dp[j] + 1 > dp[i]:
dp[i] = dp[j] + 1
O(N)O(N) (각 i마다 최대 i번)O(N^2)i에 대해 이전 모든 병사 j를 확인하여 LDS를 갱신합니다.최대 LDS 길이 찾기:
lds_length = max(dp)O(N)결과 계산:
soldiers_to_remove = N - lds_lengthO(1)총 시간 복잡도: O(N^2)
N ≤ 2,000이므로, O(N^2) 알고리즘은 약 4,000,000 연산을 수행하며, 이는 충분히 빠르게 처리 가능합니다.dp = [1] * NO(N)combat_powersO(N)총 공간 복잡도: O(N)
dp 배열과 전투력 리스트를 저장하기 위해 O(N)의 공간을 사용합니다.dp[i]는 i번째 병사를 마지막으로 하는 LDS의 길이입니다.dp[i] = max(dp[j] + 1) for all j < i where combat_powers[j] > combat_powers[i]
dp[i]는 최소 1로 시작 (자기 자신만으로 LDS를 이룰 수 있으므로).dp)을 사용하여 각 병사에 대한 LDS 길이를 저장합니다.제공된 파이썬 코드는 동적 계획법(DP)을 사용하여, 주어진 병사들의 전투력 리스트에서 최장 감소 부분 수열(LDS)의 길이를 계산하고, 이를 기반으로 필요한 열외 병사의 수를 정확하게 구합니다. 시간 복잡도는 O(N^2), 공간 복잡도는 O(N)으로, 주어진 문제의 제한 사항 (N ≤ 2,000) 내에서 매우 효율적으로 동작합니다.
O(N^2)O(N)이 접근 방식은 병사의 전투력 리스트를 순차적으로 처리하면서, 가능한 최대한 많은 병사를 남기기 위해 효율적으로 열외 병사를 결정합니다. 코드가 간결하고 이해하기 쉬우며, 동적 계획법의 기본 개념을 잘 활용하고 있어 코딩 테스트 환경에서도 효과적으로 사용할 수 있습니다.
O(N^2) 방법 외에도, 이진 탐색(Binary Search)을 활용한 O(N log N) 시간 복잡도의 방법도 존재합니다. 하지만, 현재 문제의 제한 사항에서는 O(N^2) 방법이 충분히 효율적입니다.N이 매우 큰 경우, O(N^2) 알고리즘은 비효율적일 수 있으므로, O(N log N) 알고리즘을 사용하는 것이 필요합니다. 예를 들어, 이진 탐색을 활용하여 LDS를 계산할 수 있습니다.