Gliver
알고리듬
Gliver
ally.coding@gmail.com
전체 방문자
오늘
어제
  • 분류 전체보기 (63)
    • ✏️ 알고리즘 (43)
      • 필독⭐ (2)
      • 기본 알고리즘 (7)
      • 필수 알고리즘 (6)
      • 그래프 알고리즘 (6)
      • PS 기록 (22)
    • 📐 Math (7)
      • Linear Algebra (7)
    • 📁 About .. (5)
      • About AI (0)
      • About CS (0)
      • About Develope (2)
      • 알아두면 좋은 내용들 (3)
    • 📜 기록 (2)
      • 컴공 학부생 4년 (2)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • python
  • 알고리즘 강의

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Gliver

알고리듬

강의 대표 이미지
이 글의 저자가 직접 가르치는 강의가 보고 싶다면?
⭐️ 24시간 만에 코딩테스트 정복하기 ⭐️
✏️ 알고리즘/PS 기록

[백준 25639번] 수열과 최대 상승 쿼리

2023. 3. 29. 01:13

 

25639번: 수열과 최대 상승 쿼리

길이가 $N$인 수열 $a_1, a_2, ..., a_N$이 주어졌을 때, 다음과 같은 쿼리를 수행하는 프로그램을 작성해보자. 1 k x: $a_k$를 $x$로 바꾼다. 2 l r: 구간 $[l, r]$의 최대 상승 값을 출력한다. 구간 $[l, r]$의 최

www.acmicpc.net


 

목차

  • 사전 지식
  • 문제 접근
  • 문제 풀이

 

 

사전 지식

  • 세그먼트 트리(Segment Tree)에 대한 기본 지식을 알고 있어야 한다. (링크)
  • 특히, 세그먼트 트리가 쿼리를 처리하는 과정을 이해하고 있어야 한다.

 

 

 

문제 접근

문제에서 길이가 $N$인 배열($\small{a_1, a_2, \cdots, a_N}$)과 $Q$개의 쿼리(query)가 주어진다.

이 배열에서 아래의 두 개의 query만 잘 수행하면 문제를 풀 수 있다.

  1. 1 k x: $a_k$를 $x$로 바꾼다.
  2. 2 l r: 구간 $\small{[l, r]}$의 최대 상승 값을 출력한다. (최대 상승 값의 정의는 문제를 참조하기 바란다)

 

query1 처리

query1은 배열에서 원소를 바꾸는 작업으로 arr[k] = x를 수행하면 된다.

 

query2 처리

query2을 처리하는 방법은 query1에 비해 매우 까다롭다.

 

[기초적인 방법]

기본적인 방법으로 $l, r$에 대해 $l \leq i \leq j \leq r$를 만족하는 모든 $(i, j)$의 조합을 살펴보면 구할 수 있다.

하지만, $(i, j) \ $경우의 수가 약 $_{l-r+1} \mathrm{C}_{2} \ $개 이므로 쿼리 한 번당 최대 $O(N^2)$이 걸린다.

쿼리의 개수가 $Q$개 이므로, 전체 시간 복잡도는 최악의 경우에 $O(Q \cdot N^2)$이 나온다.
따라서, 이러한 방법으로 처리하는 것은 TLE(Time Limit Exceede) 오류가 나게 된다.

 

[조금 더 효율적으로]

조금 더 생각해보면 더 효율적인 방법을 떠올릴 수 있다.

인덱스 $j$를 고정한다고 하면, $max(a_j - a_i)$를 어떻게 구할 수 있을까?
$l \leq i \leq j$에서 값이 가장 작은 $a_i$를 구하면 $max(a_j - a_i)$를 구할 수 있다.

 

즉, $l \leq j \leq r$에서 모든 $j$에 대해 $max(a_j - a_i)$의 값을 살펴보면 쿼리의 답을 구할 수 있다.

이를 구현하는 법은, 지금까지 탐색한 가장 작은 원소를 저장하며 구간 $[l, r]$을 한 번 탐색하면 된다.

int query2(int l, int r) {
   int ret = 0;
   int mn = int(1e9); // 현재 살펴본 원소 중 최소값

   for (int j=l; j<=r; j++) {
      mn = min(mn, arr[j]);
      ret = max(ret, arr[j]-mn);
   }

   return ret;
}

 

하지만, 이러한 방법을 쓴다고 해도 쿼리 한 번을 처리하는 데 최대 $O(N)$이 걸린다.

즉, 전체 시간 복잡도는 $O(Q \cdot N)$으로 여전히 TLE 오류가 나게 된다. 😭

 

여기서 알 수 있는 점은 $Q \leq 100,000 \ $이므로 query2를 $O(\log N)$ 정도에 (가깝게) 처리해야 한다.

즉, query2에서 구간 $[l, r]$을 처리할 때 일일이 살펴보는 것이 아닌 합쳐서 살펴봐야 한다는 것이다!

 

 

 

문제 풀이

query2에서 구간 $[l, r]$을 처리할 때 구간을 어떻게 합쳐서 살펴볼 수 있을까?

구간을 살펴본다고 하면 떠오르는 자료구조는 세그먼트 트리(Segment Tree)일 것이다.

하지만, 이를 사용하기 전에 먼저 알아야 할 중요한 사실이 있다.

중요한 사실

구간 $\small{[a, b]}$의 최대 상승 값과 구간 $\small{[b+1, c]}$의 최대 상승 값을 알고 있으면 구간 $\small{[a, c]}$의 최대 상승 값을 알 수 있을까? (단, $\small{a \leq b \leq c})$

답은 Yes이며, 어떻게 구할 수 있는지 이제 설명하겠다.
구간이 합쳐져도 구간 $\small{[a, b]}$에서 나오는 최대 상승 값과 구간 $\small{[b+1, c]}$에서 나오는 최대 상승값은 유효하다.
그리고, 추가적으로 살펴보아야 하는 것은 구간이 합쳐지면서 생기는 최대 상승 값이다.
이를 구하는 방법은 구간 $\small{[b+1, c]}$의 최대값에서 $\small{[a, b]}$의 최소값을 빼면 구할 수 있다.

 

즉, 정리하면 다음 중에서 최대값을 고르면 된다.

  • $\small{[a, b]}$에서 나오는 최대 상승 값
  • $\small{[b+1, c]}$에서 나오는 최대 상승값
  • $\small{[b+1, c]}$의 최대값에서 $\small{[a, b]}$의 최소값을 뺀 값

 

문제 풀이

이제 위의 중요한 사실을 이용하여 세그먼트 트리를 구성해보자.

두 구간을 통해서 합친 구간의 최대 상승값을 구할 때 필요한 값은 구간에서의 최소값, 최대값, 최대 상승 값이다.

따라서, 세그 먼트 트리의 각 노드는 (구간의 최소값, 구간의 최대값, 구간의 최대 상승 값) 정보를 담고 있어야 한다.

각 노드에 3개의 정보를 저장해야 하기 때문에 구조체를 쓰면 된다.

 

리프 노드의 인덱스는 문제에서 주어진 배열의 인덱스와 대응하며

리프 노드에 대응하는 배열에 들어 있는 원소의 값을 $x$라고 할 때, 리프 노드의 초기값은 ($x$, $x$ ,$0$)이다.

(세그먼트 트리를 업데이트하는 update()는 간단하지만, query2를 처리하는 query()의 경우 조금 까다로우니 주의하자)

 

 

 

 

'✏️ 알고리즘 > PS 기록' 카테고리의 다른 글

[백준 10167번] 금광  (2) 2023.03.30
[백준 15561번] 구간 합 최대? 2  (0) 2023.03.30
[백준 2336번] 굉장한 학생  (2) 2023.03.27
[백준 12015번] 가장 긴 증가하는 부분 수열2 - 세그먼트 트리  (2) 2023.03.27
[AtCoder] ABC295 A~D 업솔빙  (2) 2023.03.26
    '✏️ 알고리즘/PS 기록' 카테고리의 다른 글
    • [백준 10167번] 금광
    • [백준 15561번] 구간 합 최대? 2
    • [백준 2336번] 굉장한 학생
    • [백준 12015번] 가장 긴 증가하는 부분 수열2 - 세그먼트 트리
    Gliver
    Gliver

    티스토리툴바