[BOJ 14438, Python] 数列与查询 17
BOJ 14438,“数列与查询 17” 题的 Python 解法
题目链接
[BOJ 14438](https://www.acmicpc.net/problem/14438
)
分类
段树(segtree),数据结构(data_structures)
说明
我认为这是初次接触段树的人最常遇到的段树类型题目。
其优势在于,相比线性列表,它能在O(NlogN)的时间复杂度内构建出支持区间和、区间积、区间最大值等各种运算的结构,并能以O(logN)的高速访问各区间的信息。
本题主要可分为构建分段树的函数、查询区间信息的函数以及更新值的函数。
构建函数
首先来看构建分段树的函数
def make_segtree(target_list: list, result_list: list, start: int, end:int, now_node: int) -> int:
'''
최소 숫자 정보를 담는 세그먼트 트리를 생성하는 함수
'''
# 포인터가 한 곳으로 모인 경우
if start == end:
# 해당 노드는 포인터가 가리키는 인덱스의 값과 동일
result_list[now_node] = target_list[start]
# 포인터가 범위를 나타내는 경우
else:
# Devide and Conquer Algorithm
# 중간 지점 선언
mid = (start + end) // 2
# 중간 지점을 기준으로 구간을 분할하여 최소값으로 갱신
val_1 = make_segtree(target_list, result_list, start, mid, 2 * now_node)
val_2 = make_segtree(target_list, result_list, mid + 1, end, 2 * now_node + 1)
# 두 구간의 최소값을 비교하여 둘 중 최소값을 현재 노드에 기록
result_list[now_node] = min(val_1, val_2)
# 현재 노드의 값을 반환
return result_list[now_node]
,该函数基本展示了将整个列表按区间拆分,并将各区间的信息存储到分段树各节点中的过程。
由于采用了递归方式,因此会不断堆积递归栈,直到区间长度为1为止;若递归成功,则将该地址对应的单个列表值信息写入其中。
随后,通过返回的值,非叶子节点会筛选符合条件的值,最终到达根节点。
因此,根节点中存储的值就是整个区间内最符合题目要求的值。
搜索函数
接下来查看查找区间内最小值的函数:
def find_min_number(target_tree: list, start: int, end: int, left: int, right: int, now_node: int) -> int:
'''
해당 구간의 최소값을 찾는 함수
'''
# 범위 내로 포인터가 좁혀진 경우
if left <= start and right >= end:
# 현재 노드값을 반환
return target_tree[now_node]
# 범위를 아예 벗어난 경우
elif left > end or right < start:
# None 값을 반환
return None
# 범위가 걸친 경우
else:
# Devide and Conquer Algorithm
# 중간 지점 선언
mid = (start + end) // 2
# 중간 지점을 기준으로 구간을 분할하여 값을 확인
val_1 = find_min_number(target_tree, start, mid, left, right, 2 * now_node)
val_2 = find_min_number(target_tree, mid + 1, end, left, right, 2 * now_node + 1)
# 반환된 값들 중 None이 존재하는 경우
if val_1 is None and val_2 is None:
return None
elif val_1 is None:
return val_2
elif val_2 is None:
return val_1
# None이 없다면
else:
# 두 구간의 최소값 중에서 더 작은 값을 반환
return min(val_1, val_2)
。该函数的基本逻辑是:只要包含目标区间,就进行详细搜索,直到仅包含目标区间为止,并返回该值以便上级函数进行验证。
如果超出范围,则返回None。通过条件判断进行剪枝(Pruning),即可确保仅选择范围内的区间进行比较。
修改函数
最后来看反映值被修改情况的函数
def change_segtree(target_tree: list, start: int, end: int, target_idx: int, target_val: int, now_node: int) -> None:
'''
리스트에 생긴 변경점을 트리에 반영하는 함수
'''
# 포인터가 한 곳으로 모인 경우
if start == end:
# 목표로 하는 인덱스랑 동일하다면 변경값을 반영
if start == target_idx:
target_tree[now_node] = target_val
# 포인터가 범위로 주어진 경우
else:
# 목표 인덱스가 범위 내에 존재할 경우에만 갱신 가능성 존재
if start <= target_idx <= end:
# Devide and Conquer Algorithm
# 중간 지점 선언
mid = (start + end) // 2
# 중간 지점을 기준으로 변경될 수 있는 인덱스를 재조사
change_segtree(target_tree, start, mid, target_idx, target_val, 2 * now_node)
change_segtree(target_tree, mid + 1, end, target_idx, target_val, 2 * now_node + 1)
# 하위 노드들의 갱신이 진행된 이후 현재 노드의 갱신 진행
target_tree[now_node] = min(target_tree[2 * now_node], target_tree[2 * now_node + 1])
由于如果包含当前被修改值的索引的区间,则必须全部更改,因此以该索引是否包含在区间内为标准进行区分。
与生成函数类似,会不断堆叠递归栈直至缩小到单个索引,并从仅包含该索引的节点开始更新其值,在返回过程中反映修改后的值,最终检查至根节点以完成操作。
解题代码
通过上述过程,可以将题目要求的全部操作模块化,以下是完整的代码。
# 수열과 쿼리 17
import sys
from math import ceil, log2
input = sys.stdin.readline
def make_segtree(target_list: list, result_list: list, start: int, end:int, now_node: int) -> int:
'''
최소 숫자 정보를 담는 세그먼트 트리를 생성하는 함수
'''
# 포인터가 한 곳으로 모인 경우
if start == end:
# 해당 노드는 포인터가 가리키는 인덱스의 값과 동일
result_list[now_node] = target_list[start]
# 포인터가 범위를 나타내는 경우
else:
# Devide and Conquer Algorithm
# 중간 지점 선언
mid = (start + end) // 2
# 중간 지점을 기준으로 구간을 분할하여 최소값으로 갱신
val_1 = make_segtree(target_list, result_list, start, mid, 2 * now_node)
val_2 = make_segtree(target_list, result_list, mid + 1, end, 2 * now_node + 1)
# 두 구간의 최소값을 비교하여 둘 중 최소값을 현재 노드에 기록
result_list[now_node] = min(val_1, val_2)
# 현재 노드의 값을 반환
return result_list[now_node]
def find_min_number(target_tree: list, start: int, end: int, left: int, right: int, now_node: int) -> int:
'''
해당 구간의 최소값을 찾는 함수
'''
# 범위 내로 포인터가 좁혀진 경우
if left <= start and right >= end:
# 현재 노드값을 반환
return target_tree[now_node]
# 범위를 아예 벗어난 경우
elif left > end or right < start:
# None 값을 반환
return None
# 범위가 걸친 경우
else:
# Devide and Conquer Algorithm
# 중간 지점 선언
mid = (start + end) // 2
# 중간 지점을 기준으로 구간을 분할하여 값을 확인
val_1 = find_min_number(target_tree, start, mid, left, right, 2 * now_node)
val_2 = find_min_number(target_tree, mid + 1, end, left, right, 2 * now_node + 1)
# 반환된 값들 중 None이 존재하는 경우
if val_1 is None and val_2 is None:
return None
elif val_1 is None:
return val_2
elif val_2 is None:
return val_1
# None이 없다면
else:
# 두 구간의 최소값 중에서 더 작은 값을 반환
return min(val_1, val_2)
def change_segtree(target_tree: list, start: int, end: int, target_idx: int, target_val: int, now_node: int) -> None:
'''
리스트에 생긴 변경점을 트리에 반영하는 함수
'''
# 포인터가 한 곳으로 모인 경우
if start == end:
# 목표로 하는 인덱스랑 동일하다면 변경값을 반영
if start == target_idx:
target_tree[now_node] = target_val
# 포인터가 범위로 주어진 경우
else:
# 목표 인덱스가 범위 내에 존재할 경우에만 갱신 가능성 존재
if start <= target_idx <= end:
# Devide and Conquer Algorithm
# 중간 지점 선언
mid = (start + end) // 2
# 중간 지점을 기준으로 변경될 수 있는 인덱스를 재조사
change_segtree(target_tree, start, mid, target_idx, target_val, 2 * now_node)
change_segtree(target_tree, mid + 1, end, target_idx, target_val, 2 * now_node + 1)
# 하위 노드들의 갱신이 진행된 이후 현재 노드의 갱신 진행
target_tree[now_node] = min(target_tree[2 * now_node], target_tree[2 * now_node + 1])
# 수열의 크기 및 수열 정보 입력
target_length = int(input())
target_arr = ['*'] + list(map(int, input().split()))
# 수열의 구간 최소 숫자를 담을 세그먼트 트리 리스트 선언
segtree_arr = [None for _ in range(pow(2, ceil(log2(target_length) + 1)))]
# 함수를 호출하여 트리 생성
make_segtree(target_arr, segtree_arr, 1, target_length, 1)
# 퀴리 갯수 입력 및 출력 리스트 선언
query_number = int(input())
output = []
# 쿼리 실행
for _ in range(query_number):
# 각 쿼리의 정보
order_type, num_1, num_2 = map(int, input().split())
# 타입 1의 경우
if order_type == 1:
# 리스트 값을 변경 및 함수를 호출하여 트리에 반영
target_arr[num_1] = num_2
change_segtree(segtree_arr, 1, target_length, num_1, num_2, 1)
# 타입 2의 경우
elif order_type == 2:
# 함수를 호출하여 해당 구간의 최소 숫자를 출력 리스트에 추가
output.append(find_min_number(segtree_arr, 1, target_length, num_1, num_2, 1))
# 결과 출력
for result in output:
print(result)
댓글 작성
게시글에 대한 의견을 남겨 주세요.