Convex Hull Trick과 Li Chao Tree
Convex hull trick은 여러 개의 일차함수들이 있을 때, 어떤 좌표에서의 최댓값 혹은 최솟값을 빠르게 구하기 위해 사용할 수 있는 방법이다. CHT라고 부르기도 한다.
일차함수는 다양한 특징을 가져서 비교적 쉽게 관리할 수 있고, 그래서 문제에도 많이 나온다. 일차함수의 최댓값 문제에서 중요하게 사용되는 성질들은 다음과 같다.
1. 여러 일차함수들이 있을 때, 이들의 최댓값을 라고 하면, 각 일차함수 에 대해 인 값은 닫힌 구간(을 포함)으로 표현된다.
증명 생략.
2. 일차함수는 볼록하다.
이므로 볼록하면서 오목하다.
3. 일차함수들의 최댓값은 볼록하다. 일반적으로 볼록함수들의 최댓값은 볼록하다.
, 를 만족하는 두 볼록함수가 있다고 하자.
위의 두 식에 의해
이므로 볼록하다.
볼록하다는 말은 엄밀하지 않게는 기울기가 증가한다고 말할 수도 있다. 최댓값에 관여되는 일차함수들을 기울기 순서대로 늘어놓으면 각 일차함수들이 관여하는 구간들이 증가하게 되고, 일차함수들을 기울기 순서대로 일렬로 늘어놓고 관리하면 이분탐색을 통해 특정 값에서의 최댓값을 찾을 수 있다.
이 성질들을 이용하여 다음과 같은 Convex hull trick을 생각할 수 있다. 뒤에서 나오는 은 삽입 연산의 횟수, 는 최댓값 쿼리의 개수이다.
A. Incremental convex hull trick
일차함수의 삽입 연산과 에서의 최댓값 쿼리가 있다고 하자.
일차함수들을 기울기 순서대로 배열에 넣어서 관리한다고 생각하자. 다만, 인 가 없는 는 무효하기 때문에 없는 것 취급한다.
새로운 일차함수를 삽입한다면, 삽입되는 일차함수의 기울기를 기준으로 삽입될 위치를 결정할 수 있다. 삽입 후의 일차함수 배열을 라고 하자. 다음과 같은 경우가 생길 수 있다.
1. 가 무효하다.
가 보다 작다면 는 무효하다. 이때는 삽입을 하지 않아야 한다.
2. 이나 이 무효하다.
의 삽입으로 인해 바로 옆의 일차함수가 무효하게 된 경우이다. 이 경우에는 그 일차함수를 지운 후 를 다시 삽입한다. 이 경우에 무조건 일차함수가 1개씩 사라지므로 amortized 번의 연산만이 사용된다.
3. , , 이 모두 무효하지 않다.
세 일차함수가 모두 유효하다면, 가 다른 일차함수들에는 영향을 미치지 않는다. 따라서 그 자리에 그대로 삽입할 수 있다.
이 배열을 BBST로 관리한다면 2번 연산을 amortized , 나머지 연산을 에 처리할 수 있다. 에서의 최댓값 쿼리도 에 처리할 수 있다.
이 방법은 어떤 일차함수가 들어오던 온라인으로 삽입하고 쿼리하는 것이 가능하다. 그러나 BBST의 특성상 이지만 느리고 복잡하다. 또 제거가 불가능하다는 문제점이 있다. 구현을 단순하게 하기 위해서 삽입되는 일차함수나 쿼리로 들어오는 좌표에 제한 조건을 둘 수 있다.
B. Incremental convex hull trick (increasing query)
쿼리로 들어오는 좌표가 단조 증가 또는 감소한다면, 쿼리를 좀 더 간단하게 구현할 수 있다. 좌표가 증가한다는 의미는 보다 작은 쿼리는 더 이상 들어오지 않는다는 것을 의미한다. 가장 왼쪽의 두 일차함수 , 를 보자. 이 관리하는 구간은 , 가 관리하는 구간은 일 것이다. 만약 이라면 의 값이 최댓값이 된다. 이라면 앞으로의 모든 쿼리에서 이 되고, 은 더 이상 사용되지 않게 된다. 따라서 을 지워버리고 위의 과정을 반복할 수 있다.
이때의 쿼리당 시간복잡도는 일 때 이고, 일 때 일차함수가 무조건 하나 사라지므로 amortized 이다. 총 시간복잡도 로 처리할 수 있다.
C. Incremental convex hull tick (increasing slope)
삽입되는 일차함수의 기울기가 단조 증가 또는 감소한다면, BBST 없이도 구현이 가능하다. 의 삽입 연산이 들어왔다면, 이미 삽입된 일차함수들의 기울기는 의 기울기 이하일 것이다. 가장 기울기가 큰 일차함수가 관리하는 구간은 가 될 것이고, 따라서 새로 추가된 일차함수에 의해 제거되는 일차함수들은 오른쪽의 몇 개일 것이다. 가장 오른쪽에 있는 일차함수 두 개와 새로 삽입될 일차함수를 비교하여 가장 오른쪽에 있는 일차함수가 무효하다면 그 일차함수를 제거하고 반복하면 되고, 무효하지 않다면 가장 오른쪽에 일차함수를 삽입하면 된다. 이것은 스택을 이용하여 관리할 수 있고, 일차함수 삽입의 시간복잡도는 amortized 이 되고, 쿼리의 시간복잡도는 이 된다.
이분탐색을 통해 오른쪽에서 제거될 일차함수의 개수를 에 찾아서 한 번에 제거할 수도 있다. 이것을 배열을 이용하여 구현하면 amortized가 붙지 않는 대신 일차함수 삽입의 시간복잡도가 이 된다.
D. Persistent incremental convex hull trick
A, B, C의 BBST나 스택을 persistent하게 구현하여 사용할 경우, CHT를 persistent하게 사용할 수 있다.
E. Offline dynamic convex hull trick
일차함수의 제거를 처리하기 위해서 오프라인으로 segment tree를 이용할 수 있다.
segment tree를 시간 기준으로 만들면 각 노드들은 를 관리하는 노드가 될 것이다. 각각의 노드들을 A, B, C와 같은 자료구조로 만들어 각각이 CHT를 처리할 수 있도록 만든다.
오프라인 알고리즘이기 때문에 각각의 일차함수들이 관여하는 시간을 구간 로 표현할 수 있다. 에 포함되는 가장 높은 개의 노드에 해당 일차함수를 삽입하면 각 노드의 시간복잡도에 삽입을 처리할 수 있다.
쿼리 또한 쿼리가 들어온 시간 를 포함하는 개의 노드들에 쿼리를 해서 모두 합쳐주면 각 노드의 시간복잡도에 쿼리를 처리할 수 있다.
segment tree를 시간 기준으로 만드는 대신 좌표 기준으로 만들 경우 일차함수(직선)이 아닌 선분의 삽입을 처리하는 것도 가능한 등, segment tree를 통해 다양한 문제를 해결할 수 있다.
F. Li-Chao tree
Convex hull trick 글에 썼지만 Convex랑 관련이 없다…
Li-Chao segment tree라고도 부르는 것 같다. 이름에서 볼 수 있듯 segment tree의 일종인데, 각 노드들은 좌표 를 관리하며, 하나의 일차함수를 가지고 있다.
Li-Chao tree가 사용될 수 있는 상황은 쿼리로 들어오는 좌표가 유한한 경우로, 보통 쿼리들을 모두 알고 있거나, 쿼리가 범위가 정해진 정수일 때 사용할 수 있다.
최댓값을 구하고자 할 때, Li-Chao tree의 초기 상태는 모든 노드가 를 가지고 있는 상태이다. 이 트리에서 쿼리 에서의 최댓값을 구하기 위해서는 를 포함하는 모든 노드에서의 값의 최댓값을 가져오면 된다.
이제 Li-Chao tree가 위의 성질을 유지하도록 새로운 일차함수를 삽입하자.
를 관리하는 노드에 가 삽입된다고 가정하자. 일차함수가 관리하는 가 구간으로 나타나기 때문에 노드에 있던 일차함수와 새로운 일차함수 중에서 단 하나의 일차함수가 관리하는 구간만이 을 포함한다.
가 관리하는 구간에 포함된다는 뜻은, 나머지 하나의 일차함수는 의 왼쪽이나 오른쪽 중 하나만을 관리한다는 의미가 된다. 따라서 나머지 하나의 일차함수가 관리하는 구간은 또는 에 포함된다. 에 포함될 경우에는 왼쪽 자식 노드, 에 포함될 경우에는 오른쪽 자식 노드에 재귀적으로 삽입해줄 수 있다.
계속해서 재귀적으로 삽입하면 쿼리로 가능한 좌표의 유한성으로 인해 인 노드가 나타나고, 원래 있던 일차함수와 새로 삽입된 일차함수 중 하나가 무효해지는 순간이 발생하게 된다. 이때 재귀를 중지할 수 있다.
좌표로 가능한 경우의 수를 라고 할 때, Li-Chao tree의 시간복잡도는 삽입, 쿼리가 각각 가 된다. Li-Chao tree도 E의 segment tree를 이용한 것과 똑같이 선분을 삽입하는 것이 가능한데, 이때 시간복잡도는 가 된다.
일 때 A부터 E까지의 방법은 두 일차함수의 교점을 비교하는 과정이 필요하다. 교점을 정확히 비교하기 위해 정수 자료형을 사용할 경우 자료형의 범위가 를 포함해야 하는데, 많은 DP 문제에서 , 의 범위가 나와서 어쩔수 없이 실수 자료형을 사용하게 된다. 그러나 Li-Chao tree는 함수의 함수값만을 비교하기 때문에 실수 자료형을 사용하게 되는 경우가 잘 발생하지 않는다.
Li-Chao tree도 segment tree의 일종이기 때문에 동적으로 구성할 수 있다. Li-Chao tree의 특이한 점은 동적으로 트리 압축 없이 구성해도 공간복잡도가 라는 것이다.