문제 링크
https://www.acmicpc.net/problem/31503
문제
이 문제는 DP (Small)과 문제의 수 제한, 질문의 수 제한, 문제의 난이도 제한이 다른 문제이다.
DP는 DYNAMIC Porani의 약자이다.
DYNAMIC한 포라니는 가능한 문제를 DYNAMIC하게 푸는 것을 좋아한다. DYNAMIC한 문제 풀이란 문제의 번호와 난이도가 모두 증가하도록 가능한 한 많이 푸는 것이다.
방학을 맞은 포라니는 알고리즘 능력 향상을 위해 선배로부터 추천 문제 셋을 받았다. 나태한 포라니는 모든 문제를 풀고 싶지 않다. 만약 선배로부터 어떤 문제를 풀라는 지시를 받았을 때, 그 문제를 포함하여 DYNAMIC하게 문제를 풀었을 경우 몇 문제를 풀어야 하는지 알려주자.
입력
첫 번째 줄에 문제의 수 N(1 ≤ N ≤ 300,000)과 쿼리의 수 Q(1 ≤ Q ≤ N)가 공백으로 구분되어 주어진다.
두 번째 줄에 문제의 난이도를 나타내는 음이 아닌 정수 Di(1 ≤ i ≤ N; 0 ≤ Di ≤ 3 × 10^8)가 문제 번호의 순서대로 공백으로 구분되어 주어진다. 문제 번호는 1부터 시작하는 연속된 양의 정수이다.
세 번째 줄부터 Q개의 줄에 걸쳐 선배가 풀라고 한 문제의 번호 Ai(1 ≤ i ≤ Q; 1 ≤ Ai ≤ N)가 주어진다. 쿼리로 주어지는 문제의 번호는 중복되지 않는다.
출력
Q개의 줄에 걸쳐, i번째 줄에 i번째 선배의 지시에 따라 풀어야 하는 문제 수를 순서대로 출력한다.
예제 입력 1
6 6
1 2 3 4 3 2
1
2
3
4
5
6
예제 출력 1
4
4
4
4
3
2
알고리즘 분류
- 다이나믹 프로그래밍
- 이분 탐색
풀이
쿼리의 개수는 최대 300,000개까지이며, 따라서 300,000개의 LIS를 구하면 되나 싶지만 그렇게 된다면 O(n^2logn)만큼의 시간복잡도를 가지기 때문에 시간 초과가 발생할 것이다.
LIS를 2번만 구해서 O(nlogn)만큼의 시간복잡도로 해결하는 방법이 존재한다.
왼쪽에서부터 시작하는 LIS를 구하면서, 현재 인덱스까지 탐색했을 때 도출된 LIS의 길이를 기록해둔다.
오른쪽에서부터 시작하는 LIS를 구해야 하는데, 문제의 난이도만 거꾸로 반전시켜서 구하면 LIS를 구할 수가 없다. 따라서, 입력을 받으면서 미리 마이너스를 붙여 lower_bound로 이분 탐색으로 LIS를 구할 수 있도록 미리 반전된 문제 난이도 배열을 저장한다.
이제부터는 Q개의 주어진 문제 인덱스별로 해당 문제가 포함된 LIS를 구할 수 있다. 왼쪽 LIS의 현재 인덱스는 처음부터 현재 인덱스까지 나올 수 있는 LIS의 최대 길이이며, 오른쪽 LIS의 현재 인덱스는 현재 인덱스부터 끝 숫자까지 나올 수 있는 LIS의 최대 길이이다.
이 두 값을 더해주고, 현재 인덱스의 문제 난이도가 중복이 되므로 1을 뺀다. 그렇게 해서 구한 값이 쿼리문에 주어진 문제를 포함하는, DYNAMIC하게 문제를 풀었을 때의 문제 개수가 된다.
코드
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#define FASTIO cin.tie(0); cout.tie(0); ios::sync_with_stdio(false);
using namespace std;
int N, Q;
vector<int> Numbers, ReverseNumbers, Queries, LeftLisLengths, RightLisLengths;
vector<int> Answer;
void input() {
cin >> N >> Q;
Numbers.resize(N);
Queries.resize(Q);
for (int i = 0; i < N; i++) {
cin >> Numbers[i];
ReverseNumbers.push_back(-Numbers[i]);
}
for (int i = 0; i < Q; i++) {
cin >> Queries[i];
}
}
vector<int> findLisLengths(bool isReversed) {
vector<int> Result(N, 0);
vector<int> Temp;
vector<int> DP;
if (isReversed) {
Temp = ReverseNumbers;
reverse(Temp.begin(), Temp.end());
}
else {
Temp = Numbers;
}
for (int i = 0; i < N; i++) {
auto IT = lower_bound(DP.begin(), DP.end(), Temp[i]);
int Index = IT - DP.begin();
if (IT == DP.end()) {
DP.push_back(Temp[i]);
}
else {
*IT = Temp[i];
}
Result[i] = Index + 1;
}
if (isReversed) {
reverse(Result.begin(), Result.end());
}
return Result;
}
void settings() {
LeftLisLengths = findLisLengths(false);
RightLisLengths = findLisLengths(true);
for (int i = 0; i < Q; i++) {
int Length = LeftLisLengths[Queries[i] - 1] + RightLisLengths[Queries[i] - 1] - 1;
Answer.push_back(Length);
}
}
void printAnswer() {
for (int i = 0; i < Q; i++) {
cout << Answer[i] << "\n";
}
}
int main() {
FASTIO
input();
settings();
printAnswer();
return 0;
}
'BOJ > Platinum ~ Diamond' 카테고리의 다른 글
[BOJ/Platinum 4] 백준 2995 생일(C++) (1) | 2024.12.24 |
---|---|
[BOJ/Platinum 4] 백준 23035 가톨릭대는 고양이를 사랑해(C++) (1) | 2024.12.23 |
[BOJ/Platinum 5] 백준 2568 전깃줄 - 2(C++) (2) | 2024.12.22 |
[BOJ/Platinum 5] 백준 14003 가장 긴 증가하는 부분 수열 5(C++) (2) | 2024.12.21 |
[BOJ/Platinum 4] 백준 21624 Fence(C++) (3) | 2024.09.09 |