작성일:
목차
1. 세그먼트 트리 소개
세그먼트 트리는 효율적인 구간 쿼리 처리와 업데이트를 위한 데이터 구조입니다.
주로 배열과 같은 데이터 집합에서 구간의 합, 최소값, 최대값 등을 빠르게 처리할 수 있도록 설계되었습니다.
세그먼트 트리는 완전 이진 트리 형태로 구성되어 있으며, 트리의 각 노드는 특정 구간의 정보를 저장합니다.
세그먼트 트리의 주요 특징은 다음과 같습니다:
- 구간 쿼리: 지정된 범위 내의 값을 빠르게 조회할 수 있습니다.
- 업데이트 기능: 데이터가 변경될 때마다 트리를 효율적으로 업데이트할 수 있습니다.
- 비교적 낮은 메모리 사용: 배열을 사용하는 것과 비교할 때 상대적으로 적은 메모리를 필요로 합니다.
2. 문제 설명
다음과 같은 문제를 고려해보겠습니다. 정수 배열 arr
이 주어질 때, 특정 구간 [l
, r
]의 합을 구하는 쿼리와 i
번째 위치의 값을 val
로 업데이트하는 두 가지 기능을 지원하는 프로그램을 작성하세요.
문제의 입력 형식은 다음과 같습니다:
- 첫 번째 줄: 배열의 크기
N
(1 ≤N
≤ 100,000) - 두 번째 줄: 배열의 원소
arr[1], arr[2], ..., arr[N]
- 세 번째 줄: 쿼리의 수
Q
- 다음
Q
개의 줄: 각 쿼리를 나타내는 세 정수type, l, r
(type = 1: 구간 합 쿼리, type = 2: 업데이트 쿼리)
예를 들어, 다음과 같은 입력을 고려해볼 수 있습니다:
5 1 2 3 4 5 3 1 1 3 2 2 10 1 1 5
여기서 첫 번째 쿼리는 구간 [1, 3]의 합을 요청하고, 두 번째 쿼리는 두 번째 원소를 10으로 업데이트합니다. 세 번째 쿼리는 업데이트 후 구간 [1, 5]의 합을 요청합니다.
3. 문제 해결 과정
세그먼트 트리를 사용하여 이 문제를 해결하는 방법은 다음과 같습니다.
3.1. 세그먼트 트리 구성
우선 주어진 배열 arr
로부터 세그먼트 트리를 구성해야 합니다.
부모 노드는 자식 노드들의 값을 합하여 저장합니다.
트리는 다음과 같이 초기화할 수 있습니다:
class SegmentTree: def __init__(self, data): self.n = len(data) self.tree = [0] * (2 * self.n) # 리프 노드에 데이터 넣기 for i in range(self.n): self.tree[self.n + i] = data[i] # 내부 노드 계산 for i in range(self.n - 1, 0, -1): self.tree[i] = self.tree[i * 2] + self.tree[i * 2 + 1]
3.2. 구간 합 쿼리 처리
구간 합 쿼리를 처리하기 위해서는 리프 노드에서 루트 노드까지 거슬러 올라가야 합니다.
구간 [l, r]의 합을 구하기 위해 다음과 같이 구현할 수 있습니다:
def query(self, l, r): result = 0 l += self.n r += self.n + 1 while l < r: if l % 2 == 1: result += self.tree[l] l += 1 if r % 2 == 1: r -= 1 result += self.tree[r] l //= 2 r //= 2 return result
3.3. 업데이트 처리
업데이트 쿼리는 특정 인덱스의 값을 변경한 후, 해당 노드를 수정하고 부모 노드들에게도 영향을 미쳐야 합니다.
다음과 같이 구현할 수 있습니다:
def update(self, index, value): index += self.n self.tree[index] = value while index > 1: index //= 2 self.tree[index] = self.tree[index * 2] + self.tree[index * 2 + 1]
3.4. 전체 코드
이제 위의 구성 요소들을 모두 포함하는 전체 코드를 작성해봅시다:
def main(): import sys input = sys.stdin.read data = input().split() idx = 0 N = int(data[idx]); idx += 1 arr = [0] * N for i in range(N): arr[i] = int(data[idx]); idx += 1 Q = int(data[idx]); idx += 1 seg_tree = SegmentTree(arr) output = [] for _ in range(Q): query_type = int(data[idx]); idx += 1 l = int(data[idx]); idx += 1 r = int(data[idx]); idx += 1 if query_type == 1: result = seg_tree.query(l - 1, r - 1) output.append(str(result)) elif query_type == 2: seg_tree.update(l - 1, r) print('\n'.join(output)) if __name__ == "__main__": main()
4. 시간 복잡도 분석
세그먼트 트리의 시간 복잡도는 다음과 같습니다:
- 세그먼트 트리의 구성: O(N)
- 구간 합 쿼리: O(log N)
- 업데이트 쿼리: O(log N)
따라서 이 알고리즘은 대규모 데이터에서도 효율적으로 동작할 수 있습니다.
5. 결론
이번 글에서는 세그먼트 트리를 사용하여 구간 합 쿼리와 업데이트 쿼리를 처리하는 방법에 대해 알아보았습니다.
세그먼트 트리는 다양한 문제에서 유용하게 사용될 수 있는 강력한 데이터 구조입니다.
코딩 테스트 시, 구간 쿼리 관련 문제가 나오면 세그먼트 트리를 고려해보는 것이 좋습니다.