포스트

PS 일기 2024년 1월 29일 (1일 차) (15678번 연세워터파크)

PS 일기 2024년 1월 29일 (1일 차) (15678번 연세워터파크)

PS 일기 2024년 1월 29일 (1일 차)

PS 일기란?

PS 실력 향상을 목적으로 날마다 푼 문제를 회고하는 것입니다. 문제의 간단한 해법과 함께 어떤 방식으로 접근했는지, 그리고 문제의 해법을 찾는 데 결정적이었던 깨달음은 무엇이었는지 등을 기록합니다.

오늘의 문제

15678번 연세워터파크

알고리즘 분류

  • 덱 (Deque)
  • DP (dynamic programming)

문제 설명

길이가 $N$ 인 수열의 원소를 아래의 규칙에 따라 방문하여, 방문한 수열 원소들의 합의 최댓값을 찾아야 합니다. 단, 같은 항에 한 번만 방문할 수 있습니다.

  1. 시작점 $i (1 \leq i \leq N)$ 에 방문합니다.
  2. $j (1 \leq j \leq N, \vert i - j \vert \leq D)$ 에 방문합니다. ($i$ 는 직전에 방문한 항)
  3. 방문을 멈추거나 2번으로 되돌아갑니다.

문제 풀이

관측 1

한 방향으로 방문하여도 항상 정답을 구할 수 있습니다.

문제 설명에 따르면 수열을 너무 자유롭게 방문할 수 있습니다. 예를 들어 수열의 길이가 $10$ 이고 $D = 10$ 이라면 $1$ 번째 항에 방문한 후 $10$ 번째 항에 방문한 뒤 $5$ 번째 항에 방문할 수 있습니다. 이처럼 방문할 수 있는 순서가 너무 자유롭다면 탐색하기 힘들어집니다.

수열을 $x, y, z (x \lt z \lt y)$ 의 순서로 방문했다고 합시다. 방문 규칙에 따라 $\vert x - y \vert \leq D, \vert y - z \vert \leq D$ 입니다. 또한 $x \lt z \lt y$ 임으로 $\vert x - z \vert \leq D, \vert z - y \vert \leq D$ 입니다. 따라서 $x, z, y$의 순서로 방문하여도 무방합니다.

관측 2

왼쪽에서 오른쪽으로 방문 할 때 $i (1 \leq i \leq N)$에 마지막으로 방문한 경우 최댓값은 max(0, 이전 D개의 최댓값) + 수열의 $i$ 번째 항입니다.

이제 관측 1을 바탕으로 답을 구해야 합니다. 왼쪽에서 오른쪽 방향으로 방문한다고 할 때 $i (1 \leq i \leq N)$을 마지막으로 방문하였을 때 방문한 수열들의 합의 최댓값은 무엇일까요?

$i$항 이전에 방문한 항 $j (1 \leq j \leq N)$는 $i - d \leq j \leq i$임이 자명합니다. 따라서 $f(i) = i$를 마지막으로 방문하였을 때 방문한 수열들의 합의 최댓값이라면 정의하면 $f(i) = S_i + \max (\max_{k = i - D}^{k \lt i} f(k), 0)$입니다.

구현

의사표현코드

1
2
3
4
5
6
DP[n] = {0, };
For (i = 1; i <= N; i++) {
    // get_max_element(S, i) = S에서 1부터 i번째 요소 중 최댓값을 반환한다.
    DP[i] = max(0, get_max_element(DP, i - 1)) + S[i];
}
ans = get_max_element(DP, N);

이를 Naive하게 구현하면 $O(N \times D)$으로 시간초과가 납니다. get_max_element를 세그먼트 트리, 우선순위 큐 또는 덱을 이용한 최댓값 트릭으로 빠르게 구할 수 있는데, 가장 빠른 덱을 이용한 최댓값 트릭으로 구현하였습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <bits/stdc++.h>

using namespace std;

using ll = long long;

const ll INF = 1e18;

int main()
{
    ios_base::sync_with_stdio(false); cin.tie(NULL);

    int n, d;
    cin >> n >> d;

    ll ans = -INF;
    deque<pair<ll, int>> dq;
    for(int i = 0; i < n; i++) {
        ll x;
        cin >> x;

        while(!dq.empty() && dq.front().second < i - d) dq.pop_front();
        if(!dq.empty() && 0 < dq.front().first) x += dq.front().first;
        ans = max(x, ans);
        while(!dq.empty() && dq.back().first <= x) dq.pop_back();
        dq.emplace_back(x, i);
    }

    cout << ans;
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.