SOJ ONLINE JUDGE

문제 91

원형 수열 줄이기

내 상태
미제출
난이도
91번 문제 난이도 보기
Ruby V
출제자
rlatjwls7882
시간 제한
1000 ms
메모리 제한
512 MB
91번 문제 태그 보기
그리디 알고리즘정렬

양의 정수로 이루어진 길이 $N$의 원형 수열 $A$가 있다. 원형 수열에서 $A_N$의 다음 원소는 $A_1$이다.

현재 수열에 원소가 세 개 이상 남아 있을 때 다음 연산을 할 수 있다.

  • 양옆에 있는 두 원소보다 모두 크거나, 양옆에 있는 두 원소보다 모두 작은 원소 하나를 선택하여 제거한다.

원소를 제거하면 제거한 원소의 양옆에 있던 두 원소가 서로 이웃하게 된다.

원소가 두 개 남을 때까지 연산을 반복할 때, 마지막에 남은 두 원소의 차의 최댓값을 구하라.

입력

첫 번째 줄에 정수 $N$이 주어진다. $(3 \le N \le 200\,000)$

두 번째 줄에 $N$개의 정수 $A_1, A_2, \cdots, A_N$이 공백으로 구분되어 주어진다. $(1 \le A_i \le 10^9; A_i \ne A_j \ (1 \le i<j \le N))$

출력

첫 번째 줄에 마지막에 남은 두 원소의 차의 최댓값을 출력한다.

예제 입력 1

4
2 12 11 1

예제 출력 1

9

예제 입력 2

5
1 2 3 4 5

예제 출력 2

1

공식 해설 풀이 스포일러 보기 접기

$N\le200\,000$이므로 삭제 순서를 모두 시도할 수는 없다. 최종 두 원소를 남길 수 있는 조건을 원래 인접 원소들의 관계로 바꾸는 것이 핵심이다.

두 끝점 사이를 모두 지울 수 있는 조건

먼저 선형으로 나열된 경로의 양 끝 $a<b$만 남기려 한다고 하자. 내부 원소를 모두 지울 수 있을 필요충분조건은 원래 경로에 인접한 오름차순 쌍 $u,v$가 있어
\[ u\le a<b\le v \]
를 만족하는 것이다.

필요성을 보자. 극값 $t$를 지워 새 오름차순 쌍 $p<q$가 생겼다면 $t>q$ 또는 $t<p$이다. 전자에서는 삭제 직전의 오름차순 쌍 $p,t$가, 후자에서는 $t,q$가 값의 구간 $[p,q]$를 포함한다. 마지막 쌍 $a,b$에서 이 관계를 역추적하면 $[a,b]$를 포함하는 원래의 인접 오름차순 쌍을 찾는다.

충분성을 위해 조건을 만족하는 $u,v$를 고정하자. $a$부터 $u$까지와 $v$부터 $b$까지에서 내부 극값을 더 없앨 수 없을 때까지 지운다. 서로 다른 값으로 이루어진 경로에 내부 극값이 없으면 단조하며, 두 부분의 끝값에 의해 모두 감소하는 경로가 남는다.

이제 왼쪽 부분의 오른쪽 끝부터 지우면 오른쪽에 큰 값 $v$가 있으므로 매번 국소 최솟값을 지우게 된다. $a$만 남긴 뒤 오른쪽 부분의 왼쪽 끝부터 지우면 매번 국소 최댓값을 지우게 된다. 따라서 $a,b$만 남길 수 있다. $u=a$나 $v=b$이면 해당 쪽에서 지울 부분이 없다.

오름차순 간선과 내림차순 간선의 겹침

원형 수열에서 $a<b$를 남기려면 양쪽 경로를 모두 지워야 한다. 위 성질에 따라 한쪽에는 $[a,b]$를 포함하는 시계 방향 오름차순 쌍이, 다른 쪽에는 같은 구간을 포함하는 시계 방향 내림차순 쌍이 필요하다.

반대로 오름차순 쌍과 내림차순 쌍의 값 구간이 겹친다고 하자. 낮은 끝점 중 큰 값을 $a$, 높은 끝점 중 작은 값을 $b$로 두고 $a<b$라고 하자. $a,b$는 실제 원소이며 두 인접 쌍이 모두 $[a,b]$를 포함한다.

$a$는 두 쌍의 낮은 끝점 중 하나이고 $b$는 높은 끝점 중 하나이다. 따라서 $a$에서 $b$로 가는 시계 방향 경로에는 오름차순 쌍이 놓이고, 반대 경로에는 내림차순 쌍이 놓인다. 같은 쌍의 두 끝점이 선택된 경우에도 그 쌍은 한쪽 경로이고 다른 쌍은 반대 경로에 남는다. 각 경로에 앞의 성질을 적용하면 실제로 $a,b$만 남길 수 있다.

결국 오름차순 인접 쌍과 내림차순 인접 쌍의 값 구간을 하나씩 골라, 겹치는 길이를 최대화하면 된다.

작은 끝점부터 처리하기

원소를 값순으로 정렬해 작은 값 $x$부터 처리한다. 원래 위치의 다음 원소가 더 크면 그 값을 오름차순 구간의 높은 끝점 후보로, 이전 원소가 더 크면 그 값을 내림차순 구간의 높은 끝점 후보로 넣는다.

지금까지 얻은 두 방향의 높은 끝점 최댓값을 각각 $R,L$이라 하면 현재 확인할 후보는
\[ \min(L,R)-x \]
이다. 두 방향의 후보가 아직 없거나 값이 양수가 아니면 무시한다.

가능한 최종 쌍 $a<b$에 대해서는 $x=a$를 처리할 때 두 방향 모두 높은 끝점이 $b$ 이상인 구간이 등록되어 있다. 따라서 그 차 이상인 후보를 반드시 확인한다.

한편 어떤 양수 후보도 실제 두 구간의 겹침 길이보다 크지 않다. 두 낮은 끝점의 최댓값은 $x$ 이하이고, 높은 끝점의 최솟값은 $\min(L,R)$이기 때문이다. 그 겹침은 앞에서 실제로 남길 수 있음을 보였으므로 후보가 최적값을 넘을 수도 없다. 따라서 후보의 최댓값이 정확히 답이다.

원래 양옆 위치만 확인하며 첫 원소와 마지막 원소도 이웃으로 처리한다. 시간복잡도는 $O(N\log N)$, 공간복잡도는 $O(N)$이다.

구현 · 언어별 풀이

C++ 구현

정렬할 대상은 원소의 값과 원래 인덱스의 쌍이다. 양옆 값은 정렬된 배열이 아니라 원래 배열에서 확인하고, 인덱스는 원형으로 처리한다. 두 방향의 최댓값을 갱신한 뒤 두 후보가 모두 있을 때만 차를 계산한다.

C 구현

(값,원래 위치)를 정렬하고 원래 좌우 이웃만 조사한다. sorted 순서의 이웃으로 바꾸거나 연결 리스트에서 원소를 삭제하지 않는다. 입력이 양수라 큰 값 이웃이 없을 때의 방향 표식은 0으로 둘 수 있다.

Python 구현

(값,위치) 튜플을 정렬해 작은 값부터 처리한다. 원형 이웃 인덱스는 (i-1)%N, (i+1)%N이다. 큰 이웃 방향의 최대값을 갱신하면서 원래 배열의 인접 관계를 유지한다.

Java 구현

값과 원래 위치를 함께 정렬한다. 좌우는 (i+N-1)%N, (i+1)%N으로 구한다. 값 기준 처리 순서와 원래 배열의 이웃 순서를 혼동하지 않는다.

Rust 구현

정렬된 (값,usize 위치) 튜플을 순회한다. 왼쪽 인덱스는 (i+N-1)%N으로 구해 i=0의 언더플로를 피한다. 이웃 값 조회는 변하지 않은 원래 배열에서 한다.

JavaScript 구현

원래 수열을 유지하고, 위치 번호만 a[i] - a[j] 비교로 정렬해 값이 작은 순서대로 처리한다. 원소 배열 자체를 정렬하면 원형 수열의 이웃 정보가 사라진다.

현재 위치의 양 이웃은 (i + n - 1) % n, (i + 1) % n으로 찾는다. 다음 이웃이 크면 오른쪽 방향 높은 끝점, 이전 이웃이 크면 왼쪽 방향 높은 끝점의 최대값을 갱신하고 Math.min(left, right) - a[i]를 후보로 확인한다.

두 방향 후보가 없는 상태는 $0$으로 둘 수 있다. 입력값이 양수라 이때 만들어지는 음수 후보는 초기 답 $0$을 늘리지 않는다. 값과 차이는 $10^9$ 이하라 Number로 정확하며, 원형 링크 삭제나 treap은 필요 없다.

제출

편집기에서 나가려면 Esc를 누른 뒤 Tab 또는 Shift와 Tab을 누르세요.