https://www.acmicpc.net/problem/11053
문제:
수열 A가 주어졌을 때, 가장 긴 증가하는 부분 수열을 구하기
e.g. 수열 A = {10, 20, 10, 30, 20, 50} , 가장 긴 증가하는 부분 수열은 A = {10, 20, 10, 30, 20, 50} 이고, 길이는 4
입력
첫째 줄 수열 A의 크기 N (1 ≤ N ≤ 1,000)
둘째 줄 수열 A를 이루고 있는 Ai (1 ≤ Ai ≤ 1,000)
출력
수열 A의 가장 긴 증가하는 부분 수열의 길이
LIS(최장 증가 부분 수열, Longest Increasing Subsequence) 알고리즘 :
주어진 수열에서 일부 원소를 선택하여 순서를 유지하면서 가장 긴 증가하는 부분 수열
동적 계획법(DP, Dynamic Programming) 을 활용하여 구현
#include <iostream>
#include <vector>
using namespace std;
int n;
vector<int> Nums;
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 0; i < n; i++)
{
int a;
cin >> a;
Nums.push_back(a);
}
vector<int>IncreaseNums(n, 1);
//for (auto a : Nums) cout << a<<" ";
for (int i = 1; i < n; i++)
{
for (int j = 0; j < i; j++)
{
if (Nums[j] < Nums[i])
{
// 이전 LIS 길이에 1 추가
IncreaseNums[i] = max(IncreaseNums[i], IncreaseNums[j]+1);
}
}
}
// 가장 긴 증가 부분 수열의 길이 찾기
int result = 0;
for (int i = 0; i < n; i++)
{
result = max(result, IncreaseNums[i]);
}
cout << result << "\n";
return 0;
}
i Nums[i] 가능한 j 값 Nums[j] 조건 만족 IncreaseNums 업데이트
1 | 20 | j=0 (10) | ✅ (10 < 20) | IncreaseNums[1] = 2 |
2 | 10 | j=0,1 | ❌ (10보다 작은 값 없음) | IncreaseNums[2] = 1 |
3 | 30 | j=0,1,2 | ✅ (10, 20 < 30) | IncreaseNums[3] = 3 |
4 | 20 | j=0,1,2,3 | ✅ (10 < 20) | IncreaseNums[4] = 2 |
5 | 50 | j=0,1,2,3,4 | ✅ (10, 20, 30 < 50) | IncreaseNums[5] = 4 |
Index: 0 1 2 3 4 5
Nums: [10 20 10 30 20 50]
DP: [1 2 1 3 2 4]
vector<int> IncreaseNums(n, 1);
초기 상태
IncreaseNums = [1, 1, 1, 1, 1, 1]
IncreaseNums[1] = max(1, 1 + 1) = 2
현재 상태
IncreaseNums = [1, 2, 1, 1, 1, 1]
현재 상태
IncreaseNums = [1, 2, 1, 1, 1, 1]
IncreaseNums[3] = max(1, 1 + 1) = 2
IncreaseNums[3] = max(2, 2 + 1) = 3
IncreaseNums[3] = max(3, 1 + 1) = 3
현재 상태
IncreaseNums = [1, 2, 1, 3, 1, 1]
IncreaseNums[4] = max(1, 1 + 1) = 2
IncreaseNums[4] = max(2, 1 + 1) = 2
현재 상태
IncreaseNums = [1, 2, 1, 3, 2, 1]
IncreaseNums[5] = max(1, 1 + 1) = 2
IncreaseNums[5] = max(2, 2 + 1) = 3
IncreaseNums[5] = max(3, 1 + 1) = 3
IncreaseNums[5] = max(3, 3 + 1) = 4
IncreaseNums[5] = max(4, 2 + 1) = 4
최종 상태
IncreaseNums = [1, 2, 1, 3, 2, 4]
LIS의 길이 = 4
LIS 경로: {10 → 20 → 30 → 50} 🚀
이해되었어? 더 설명이 필요하면 말해줘! 😃
아래 코드는 이진 탐색을 활용하여 LIS를 O(N log N)에 구하는 방법입니다.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int n;
cin >> n;
vector<int> A(n);
vector<int> lis; // LIS 저장 (이분 탐색 활용)
for (int i = 0; i < n; i++)
{
cin >> A[i];
auto it = lower_bound(lis.begin(), lis.end(), A[i]);
if (it == lis.end()) // A[i]가 LIS의 마지막 값보다 크면 추가
{
lis.push_back(A[i]);
}
else // LIS 내부에서 적절한 위치에 값 교체
{
*it = A[i];
}
}
cout << lis.size() << "\n"; // LIS 길이 출력
return 0;
}
방법 시간 복잡도 특징
DP (O(N^2)) | O(N2)O(N^2) | dp[i] 배열을 이용하여 LIS 길이 계산 |
DP + 이분 탐색 (O(N log N)) | O(NlogN)O(N \log N) | lower_bound() 활용, LIS 길이만 구함 |
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int n, lis[1001], len, num;
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(0);
cin >> n;
fill(lis, lis + 1001, 0);
for (int i = 0; i < n; i++)
{
cin >> num;
auto lowerpos = lower_bound(lis, lis + len, num);
if (lowerpos == lis+len) len++;
*lowerpos = num;
//cout << "*lowerpos : "<< *lowerpos << '\n';
//cout << "lowerpos : " << lowerpos << endl;
for (int j = 0; j < n; j++)
{
//cout << lis[j] << " ";
}
//cout << "\n";
}
cout << len << endl;
vector<int> result;
for (int i = 0; i < len; i++)
{
result.push_back(lis[i]);
}
for (int i = 0; i < len; i++)
{
cout << result[i] <<" ";
}
return 0;
}
lower_bound는 → O(log N)
n개의 숫자를 입력받고 lower_bound()를 호출하는 반복문
따라서 전체 시간 복잡도는:
O(n log n)
BJ 2225 합분해 (0) | 2025.02.20 |
---|---|
BJ9251 LCS(Longest Common Subsequence, 최장 공통 부분 수열) (0) | 2025.02.19 |
백준 11399 그리디 알고리즘 (0) | 2025.02.11 |
백준 27961 마도카 고양이 (0) | 2025.02.11 |
백준 // 15686// 치킨 거리 //완전 탐색 하기~ (0) | 2025.02.07 |