정수를 담고 있는 배열 Arr[]과 쿼리를 담고 있는 2차원 배열 Q가 주어집니다. 각 쿼리는 세 개의 값, 즉 lpos(시작 위치), rpos(끝 위치), K(최대 이동 횟수)를 포함합니다.
한 번의 스텝으로 인덱스 i에서 다음 인덱스 i+1로 이동하거나, 현재 인덱스에 머무를 수 있습니다. lpos에서 rpos까지는 최대 K번의 스텝 안에서만 이동할 수 있으며, 가장 왼쪽 숫자를 포함해 각 스텝마다 방문한 숫자를 모두 더합니다. 목표는 최대 K번의 이동 안에서 합을 최대화하는 것입니다. 만약 K번의 스텝으로 lpos에서 rpos까지 이동하는 것이 불가능하다면 "NO"를 출력해야 합니다.
입출력 예시 살펴보기
입력 − Arr[] = {1, 2, 4, -1};
Q[][3] = { { 0, 2, 2 }, { 0, 2, 1 }, { 3, 3, 1 }, { 0, 2, 3 } };
출력 −
Query 1: 7
Query 2: NO
Query 3: NO
Query 4: 11
첫 번째 쿼리 설명
인덱스 0에서 2까지 최대 2번의 스텝으로 이동할 수 있습니다.
- Step 1: 인덱스 0 → 1 (1 + 2 = 3)
- Step 2: 인덱스 1 → 2 (3 + 4 = 7)
두 번째 쿼리 설명
인덱스 0에서 2까지는 거리가 2이므로 최대 1번의 스텝으로는 도달할 수 없습니다. 따라서 "NO"를 출력합니다.
세 번째 쿼리 설명
인덱스 3에서 3으로 이동하려면 시작점과 끝점이 같아 이동 자체가 불가능합니다. 따라서 "NO"를 출력합니다.
네 번째 쿼리 설명
인덱스 0에서 2까지 최대 3번의 스텝으로 이동할 수 있습니다.
- Step 1: 인덱스 0 → 1 (1 + 2 = 3)
- Step 2: 인덱스 1 → 2 (3 + 4 = 7)
- Step 3: 인덱스 2에 머무름 (7 + 4 = 11)
남은 스텝 동안 최댓값이 있는 위치에 머물면서 합을 추가로 늘릴 수 있다는 점이 핵심입니다.
두 번째 입출력 예시
입력 − Arr[] = { 1, 2, 3, 3, 2 }; Q[][3] = { { 0, 3, 2 }, { 1, 4, 3 } };
출력 −
Query 1: NO
Query 2: 10
첫 번째 쿼리 설명
인덱스 0에서 3까지는 거리가 3이므로 최대 2번의 스텝으로는 도달할 수 없습니다. "NO"를 출력합니다.
두 번째 쿼리 설명
인덱스 1에서 4까지 최대 3번의 스텝으로 이동할 수 있습니다.
- Step 1: 인덱스 1 → 2 (2 + 3 = 5)
- Step 2: 인덱스 2 → 3 (5 + 3 = 8)
- Step 3: 인덱스 3 → 4 (8 + 2 = 10)
풀이 접근 방식
이 문제는 세그먼트 트리(Segment Tree)를 활용해 lpos부터 rpos 범위 내의 최댓값을 빠르게 찾고, 접두사 합(Prefix Sum) 배열을 사용해 구간 합을 계산하는 방식으로 해결할 수 있습니다.
- 입력 배열 Arr[]과 쿼리 행렬 Q[][]를 받습니다.
- 세그먼트 트리 구현을 위해 크기가 5 * length인 sgTree[] 배열을 선언합니다.
- 접두사 합을 저장할 pSum[length] 배열을 준비합니다.
createTree 함수 — 세그먼트 트리 생성
- createTree(int min, int max, int pos, int sgT[], int arr[], int len) 함수는 세그먼트 트리의 값을 생성합니다.
- min == max인 경우 리프 노드이므로 sgT[pos] = arr[max]를 설정합니다.
- midd = (min + max) / 2로 중간 지점을 계산합니다.
- 왼쪽과 오른쪽 서브트리에 대해 createTree(min, midd, loc1, ...)와 createTree(midd + 1, max, loc2, ...)를 재귀 호출합니다. 여기서 loc1 = 2*pos+1, loc2 = 2*pos+2입니다.
- tmp1 = sgT[loc1], tmp2 = sgT[loc2]를 구한 뒤, 둘 중 더 큰 값으로 sgT[pos]를 갱신합니다.
preSum 함수 — 접두사 합 계산
- preSum(int pSum4[], int arr4[], int len4) 함수는 입력 배열을 받아 for 루프로 접두사 합 배열을 갱신합니다.
- 인덱스 1부터 마지막까지 pSum4[j] = pSum4[j - 1] + arr4[j]로 업데이트합니다.
resQuery 함수 — 쿼리 결과 처리
- resQuery(int len3, int arr3[], int sgT3[], int pSum3[], int q1[][3], int qlen1) 함수는 모든 입력값을 받아 각 쿼리의 결과를 출력합니다.
- 내부적으로 solQuery()를 호출하여 for 루프로 각 쿼리를 하나씩 해결합니다.
solQuery 함수 — 개별 쿼리 해결
- rpos - lpos > k이면 이동이 불가능하므로 -1을 반환합니다.
- maxVal = findMax(0, len2 - 1, lpos, rpos, 0, sgT2, arr2, len2)로 범위 내 최댓값을 구합니다.
- maxVal < 0이면 maxVal을 0으로 설정합니다. 음수 위치에 머무는 것보다 이동을 멈추는 것이 유리하기 때문입니다.
- sum = pSum2[rpos]로 구간 끝까지의 누적 합을 구하고, lpos > 0이면 sum -= pSum2[lpos - 1]로 시작 지점 이전 값을 뺍니다.
- 최종 결과는 result = sum + (k - (rpos - lpos)) * maxVal입니다. 즉, 남는 스텝만큼 최댓값을 반복해서 더합니다.
findMax 함수 — 범위 내 최댓값 탐색
- findMax(int start, int end, int min1, int max1, int pos1, int sgT1[], int arr1[], int len1) 함수는 lpos와 rpos 사이의 최댓값을 반환합니다.
- min1 <= start이고 max1 >= end이면 현재 노드가 쿼리 범위에 완전히 포함되므로 sgT1[pos1]을 바로 반환합니다.
- end < min1 또는 start > max1이면 범위를 벗어난 것이므로 INT_MIN을 반환합니다.
- 그 외의 경우 왼쪽과 오른쪽 서브트리에 대해 재귀 호출하여 lmax와 rmax를 구하고, 둘 중 큰 값을 반환합니다.
마지막으로 각 쿼리의 결과가 출력되며, 해가 존재하지 않으면 "NO"가 출력됩니다.
예제 코드
#include <bits/stdc++.h>
using namespace std;
// 세그먼트 트리 생성
void createTree(int min, int max, int pos,
int sgT[], int arr[], int len){ if (min == max) {
sgT[pos] = arr[max];
return;
}
int midd = (min + max) / 2;
int loc1=2*pos+1;
int loc2=2*pos+2;
createTree(min, midd, loc1, sgT, arr, len);
createTree(midd + 1, max, loc2, sgT, arr, len);
int tmp1=sgT[loc1];
int tmp2=sgT[loc2];
sgT[pos] = tmp1>tmp2 ? tmp1 : tmp2 ;
}
// 범위 내 최댓값 찾기
int findMax(int start, int end, int min1, int max1, int pos1, int sgT1[], int arr1[], int len1){
int middle;
if (min1 <= start)
{ if( max1 >= end){
return sgT1[pos1];
}
}
if (end < min1 || start > max1)
{ return INT_MIN; }
middle = (start + end) / 2;
int loc1=2 * pos1 + 1;
int loc2=2 * pos1 + 2;
int lmax = findMax(start, middle, min1, max1, loc1, sgT1, arr1, len1);
int rmax = findMax(middle + 1, end, min1, max1, loc2, sgT1, arr1, len1);
int res=lmax>rmax?lmax:rmax;
return res;
}
// 개별 쿼리 해결
int solQuery(int lpos, int rpos, int k, int len2, int arr2[], int sgT2[], int pSum2[]){
int result;
if (rpos - lpos > k)
{ return -1; }
int maxVal = findMax(0, len2 - 1, lpos, rpos, 0, sgT2, arr2, len2);
if (maxVal < 0)
{ maxVal = 0; }
int sum = pSum2[rpos];
if (lpos > 0)
{ sum -= pSum2[lpos - 1]; }
result = sum + (k - (rpos - lpos)) * maxVal;
return result;
}
// 모든 쿼리 결과 출력
void resQuery(int len3, int arr3[], int sgT3[],
int pSum3[], int q1[][3], int qlen1){
int i;
int result;
for (i = 0; i < qlen1; i++) {
result = solQuery(q1[i][0], q1[i][1],q1[i][2], len3, arr3, sgT3, pSum3);
if (result == -1)
{ cout <<endl<<"Query "<<i+1<<": "<<"NO"; }
else
{ cout <<endl<<"Query "<<i+1<<": "<<result; }
}
}
// 접두사 합 계산
void preSum(int pSum4[], int arr4[], int len4){
pSum4[0] = arr4[0];
int j;
for (j = 1; j < len4; j++){
pSum4[j] = pSum4[j - 1] + arr4[j];
}
}
int main(){
int Arr[] = {1, 2, 4, -1 };
int length = sizeof(Arr) / sizeof(Arr[0]);
int sgTreee[5 * length];
createTree(0, length - 1, 0, sgTreee, Arr, length);
int pSum[length];
preSum(pSum, Arr, length);
int Q[][3] = { { 0, 2, 2 },
{ 0, 2, 1 },
{ 3, 3, 1 },
{ 0, 2, 3} };
int qlen = sizeof(Q) / sizeof(Q[0]);
resQuery(length, Arr, sgTreee, pSum, Q, qlen);
return 0;
}
실행 결과
위 코드를 실행하면 다음과 같은 출력이 생성됩니다.
Query 1: 7
Query 2: NO
Query 3: NO
Query 4: 11