Codeforce #765 (Div. 2)의 D. Binary Spiders를 풀다가 익혀두면 좋을 것 같은 trie의 활용법이 있어 정리해보고자 한다.

간단한 문제 설명

k와 set of numbers가 주어진다. Set of numbers에서 최대한 많은 원소를 선택해 subset을 만들되, subset의 모든 원소의 pairwise xor이 k 이상이어야 한다.

문제 풀이 1 - minimum pairwise xor

다음은 editorial의 내용을 조금 더 이해하기 쉽게 풀어서 다시 쓴 것이다.

먼저 set of numbers에서 minimum pairwise xor을 찾아야 한다고 해보자. 나이브하게 모든 pair에 대해 확인하면 O(n2)이겠지만, 원소들을 정렬한 후 인접한 원소끼리의 xor만 고려하는 방법으로 O(nlogn)만에 minimum pairwise를 찾을 수 있다.

증명: 어떤 세 숫자 a>b>c가 있다고 하자. 그러면 ac가 서로 다른 bit를 가지는 첫 번째 위치가 있을 것이다. a>c 이므로 a는 bit 1을 가지고, c는 bit 0을 가진다. 또 이 위치에서 ac는 첫 번째 1 bit을 가진다. a>b>c이므로 b 역시 해당 위치까지는 prefix가 a, c와 같고, 해당 위치에서 bit가 1 혹은 0일 것이다. 따라서 abbc둘 중 하나는 해당 위치 bit가 0이므로 ac보다 작다. 귀납적으로 set of numbers에도 적용할 수 있다.

이 성질을 이용하면 먼저 set of numbers를 정렬한 후 다음과 같은 O(n2) dp식을 짤 수 있다. dpi=max(dpj)+1  (j<i and ajaik) 이때 dpii번째 원소 ai가 subset의 가장 큰 수일 때 가능한 subset 크기의 최대이다.

풀이의 시간복잡도가 O(n2)인 이유는 jajaik를 만족하는지 모두 확인해야 하기 때문이다. 이때 시간 복잡도를 O(nlogmaxai)로 줄이기 위해 trie를 사용할 수 있다.

각 원소의 bit로 0/1 trie를 구성하자. 모든 vertex는 자신의 subtree에 들어 있는 모든 원소 aj에 대해 maxdpj 값을 관리한다. 그러면 새롭게 dpi를 구하기 위해서 (즉 ajaikj에 대해 max(dpj)를 구하기 위해) trie를 탐색하자. 이때 탐색하는 경로와 ai의 xor이 k와 같도록 유지하면서 경로를 택한다. 예를 들어, ai=01011, k=00101이라면 x=01110일 때 xai=k이므로 x=01110의 경로대로 탐색한다. 즉 k의 bit가 0이면 ai의 bit와 같게, k의 bit가 1이면 ai의 bit와 반대로 탐색한다.

경로를 내려가는 도중 k의 bit가 0일 때, 탐색하지 않는 경로를 만약 택한다면 그 경로와 ai의 xor이 1이 되므로, 이 경로의 subtree에 있는 모든 원소 ajajaik를 만족한다는 뜻이다. 따라서 k의 bit가 0일 때 마다 가지 않는 경로의 vertex의 maxdpj값을 새로운 dpi의 후보로 고려해 주면 된다. 또 leaf에 다다르면 xor이 k와 같은 상태이므로 이 leaf node에서의 maxdpj 값 역시 고려해야 한다.

이렇게 dpi=max(dpj)+1를 구하고 나면, 이제 ai의 경로를 따라서 다시 trie를 탐색하며 경로상의 vertex에서 maxdpj을 업데이트해 주면 된다.

실제 풀이를 보면 이해가 더 쉬울 수 있다.

문제 풀이 2 - maximum pairwise xor

다음은 tourist의 풀이를 조금 변형해 정리한 것이다.

흥미롭게도 문제를 조금만 다른 시각으로 접근하면 위와 정 반대로 maximum pairwise xor을 빠르게 구해야 하는 문제로 환원된다.

k의 첫 번째 1이 x 번째 bit일 때, set of numbers의 각 원소를 x 번째 초과의 bit와 x 번째 이하의 bit로 나누자. 예를 들어 k=00101일 때

a1=00|010
a2=00|011
a3=00|101
a4=01|110
a5=11|011

처럼 prefix와 suffix로 나누는 것이다. 이때 prefix가 다른 원소끼리는 xor이 무조건 k 이상인 것을 알 수 있다. 또 prefix가 같은 원소는 3개 이상 subset에 들어갈 수 없고 (prefix가 같은 원소가 3개 이상이라면 xor의 x 번째 bit이 0인 쌍이 한 개 이상이기 때문에), suffix에 따라 한 개 혹은 두 개 들어갈 수 있음을 알 수 있다. 이 prefix가 같은 그룹에서 maximum pairwise xor를 구해 그것이 k 이상이라면 두 개의 원소를 subset에 넣을 수 있다는 뜻 이다. 이것 역시 trie를 이용해 구할 수 있다.

먼저 prefix가 같은 그룹의 모든 원소를 이용해 trie를 만들자. ai와 xor을 했을 때 가장 큰 값을 내는 aj를 trie를 탐색하며 구하기 위해서는, ai와 bit가 최대한 다른 경로로 탐색을 하면 된다. 예를 들어 ai의 bit가 1일 때 0으로 가는 경로가 있다면 이 경로로 탐색한다. 그렇게 해서 leaf node까지 도달하면 이 노드에 대응하는 aj에 대해 ajai가 최대 xor이고, 이것이 k 이상인지 확인하면 된다. 하나의 원소에 대해 탐색이 O(logmaxai)만큼 걸리므로 모든 원소에 대해 탐색을 하면 maximum pairwise xor를 O(nlogmaxai)에 구할 수 있다.

결론

Trie의 여러 활용법에 대해 조금더 고찰할 수 있는 문제였다.